Merge pull request #162 from snowball-tools/ayungavis/T-4943-product-reskin-confirmdialog-styling

This commit is contained in:
Wahyu Kurniawan 2024-03-25 23:04:22 +07:00 committed by GitHub
commit 78f04c3669
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1210 additions and 268 deletions

View File

@ -0,0 +1,30 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface CancelDeploymentDialogProps extends ConfirmDialogProps {}
export const CancelDeploymentDialog = ({
open,
handleCancel,
handleConfirm,
...props
}: CancelDeploymentDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Cancel deployment?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, cancel deployment"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em tracking-[-0.006em]">
This will halt the deployment and you&apos;ll have to start the process
from scratch.
</p>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,90 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import { Deployment, Domain } from 'gql-client';
import React from 'react';
import DeploymentDialogBodyCard from 'components/projects/project/deployments/DeploymentDialogBodyCard';
import { Button } from 'components/shared/Button';
import {
ChevronDoubleDownIcon,
LinkChainIcon,
} from 'components/shared/CustomIcon';
import { TagProps } from 'components/shared/Tag';
interface ChangeStateToProductionDialogProps extends ConfirmDialogProps {
deployment: Deployment;
newDeployment?: Deployment;
domains: Domain[];
}
export const ChangeStateToProductionDialog = ({
deployment,
newDeployment,
domains,
open,
handleCancel,
handleConfirm,
...props
}: ChangeStateToProductionDialogProps) => {
const currentChip = {
value: 'Live Deployment',
type: 'positive' as TagProps['type'],
};
const newChip = {
value: 'New Deployment',
type: 'attention' as TagProps['type'],
};
return (
<ConfirmDialog
{...props}
handleCancel={handleCancel}
open={open}
handleConfirm={handleConfirm}
>
<div className="flex flex-col gap-7">
<div className="flex flex-col gap-3">
<p className="text-sm text-elements-high-em tracking-[-0.006em]">
Upon confirmation, this deployment will be changed to production.
</p>
<DeploymentDialogBodyCard
deployment={deployment}
chip={newDeployment ? currentChip : undefined}
/>
{newDeployment && (
<>
<div className="flex items-center justify-between w-full text-elements-info">
{Array.from({ length: 7 }).map((_, index) => (
<ChevronDoubleDownIcon key={index} />
))}
</div>
<DeploymentDialogBodyCard
deployment={newDeployment}
chip={newChip}
/>
</>
)}
</div>
<div className="flex flex-col items-start gap-3">
<p className="text-sm text-elements-high-em tracking-[-0.006em]">
The new deployment will be associated with these domains:
</p>
{domains.length > 0 &&
domains.map((value) => {
return (
<Button
as="a"
href={value.name}
leftIcon={<LinkChainIcon size={18} />}
variant="link"
key={value.id}
>
{value.name}
</Button>
);
})}
</div>
</div>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,42 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteDomainDialogProps extends ConfirmDialogProps {
projectName: string;
domainName: string;
}
export const DeleteDomainDialog = ({
projectName,
domainName,
open,
handleCancel,
handleConfirm,
...props
}: DeleteDomainDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete domain?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, delete domain"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em">
Once deleted, the project{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{projectName}
</span>{' '}
will not be accessible from the domain{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{domainName}
</span>
.
</p>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,36 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteVariableDialogProps extends ConfirmDialogProps {
variableKey: string;
}
export const DeleteVariableDialog = ({
variableKey,
open,
handleCancel,
handleConfirm,
...props
}: DeleteVariableDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete variable"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm delete"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-mid-em">
Are you sure you want to delete the variable{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{variableKey}
</span>
?
</p>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,36 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DeleteWebhookDialogProps extends ConfirmDialogProps {
webhookUrl: string;
}
export const DeleteWebhookDialog = ({
webhookUrl,
open,
handleCancel,
handleConfirm,
...props
}: DeleteWebhookDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete webhook?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm delete"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-mid-em">
Are you sure you want to delete{' '}
<span className="text-sm font-mono text-elements-high-em px-0.5">
{webhookUrl}
</span>
?
</p>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,30 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface DisconnectRepositoryDialogProps extends ConfirmDialogProps {}
export const DisconnectRepositoryDialog = ({
open,
handleCancel,
handleConfirm,
...props
}: DisconnectRepositoryDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Disconnect repository?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm disconnect"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em">
Any data tied to your Git project may become misconfigured. Are you sure
you want to continue?
</p>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,38 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
import { formatAddress } from 'utils/format';
interface RemoveMemberDialogProps extends ConfirmDialogProps {
memberName: string;
ethAddress: string;
emailDomain: string;
}
export const RemoveMemberDialog = ({
memberName,
ethAddress,
emailDomain,
open,
handleCancel,
handleConfirm,
...props
}: RemoveMemberDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Remove member?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, remove member"
handleConfirm={handleConfirm}
confirmButtonProps={{ variant: 'danger' }}
>
<p className="text-sm text-elements-high-em">
Once removed, {formatAddress(memberName)} ({formatAddress(ethAddress)}@
{emailDomain}) will not be able to access this project.
</p>
</ConfirmDialog>
);
};

View File

@ -0,0 +1,47 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import React from 'react';
interface TransferProjectDialogProps extends ConfirmDialogProps {
projectName: string;
from: string;
to: string;
}
export const TransferProjectDialog = ({
projectName,
from,
to,
open,
handleCancel,
handleConfirm,
...props
}: TransferProjectDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Transfer project?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle="Yes, confirm transfer"
handleConfirm={handleConfirm}
>
<p className="text-sm text-elements-high-em">
Upon confirmation, your project{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{projectName}
</span>{' '}
will be transferred from{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{from}
</span>{' '}
to{' '}
<span className="text-sm font-mono text-elements-on-secondary bg-controls-secondary rounded px-0.5">
{to}
</span>
.
</p>
</ConfirmDialog>
);
};

View File

@ -12,6 +12,7 @@ import { useNavigate } from 'react-router-dom';
import { useCombobox } from 'downshift';
interface ProjectSearchBarDialogProps extends Dialog.DialogProps {
open?: boolean;
onClose?: () => void;
onClickItem?: (data: Project) => void;
}

View File

@ -1,14 +1,12 @@
import React, { useCallback, useEffect } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { Typography } from '@material-tailwind/react';
import { DeployStep, DeployStatus } from './DeployStep';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import ConfirmDialog from 'components/shared/ConfirmDialog';
import { Stopwatch, setStopWatchOffset } from 'components/StopWatch';
import { Heading } from 'components/shared/Heading';
import { Button } from 'components/shared/Button';
import { ClockOutlineIcon, WarningIcon } from 'components/shared/CustomIcon';
import { CancelDeploymentDialog } from 'components/projects/Dialog/CancelDeploymentDialog';
const TIMEOUT_DURATION = 5000;
const Deploy = () => {
@ -55,19 +53,11 @@ const Deploy = () => {
>
Cancel
</Button>
<ConfirmDialog
dialogTitle="Cancel deployment?"
handleOpen={handleOpen}
<CancelDeploymentDialog
handleCancel={handleOpen}
open={open}
confirmButtonTitle="Yes, Cancel deployment"
handleConfirm={handleCancel}
color="red"
>
<Typography variant="small" placeholder={''}>
This will halt the deployment and you will have to start the process
from scratch.
</Typography>
</ConfirmDialog>
/>
</div>
<div>

View File

@ -1,17 +1,23 @@
import React from 'react';
import { Deployment } from 'gql-client';
import { Typography, Chip, Card } from '@material-tailwind/react';
import { color } from '@material-tailwind/react/types/components/chip';
import { relativeTimeMs } from '../../../../utils/time';
import { relativeTimeMs } from 'utils/time';
import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants';
import { formatAddress } from '../../../../utils/format';
import {
BranchStrokeIcon,
ClockOutlineIcon,
CommitIcon,
} from 'components/shared/CustomIcon';
import { Avatar } from 'components/shared/Avatar';
import { getInitials } from 'utils/geInitials';
import { OverflownText } from 'components/shared/OverflownText';
import { Tag, TagProps } from 'components/shared/Tag';
interface DeploymentDialogBodyCardProps {
deployment: Deployment;
chip?: {
value: string;
color?: color;
type?: TagProps['type'];
};
}
@ -19,31 +25,54 @@ const DeploymentDialogBodyCard = ({
chip,
deployment,
}: DeploymentDialogBodyCardProps) => {
const commit =
deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH) +
' ' +
deployment.commitMessage;
return (
<Card className="p-2 shadow-none" placeholder={''}>
<div className="flex flex-col gap-4 px-4 py-4 rounded-xl bg-base-bg-emphasized text-elements-low-em">
{chip && (
<Chip
className={`w-fit normal-case font-normal`}
size="sm"
value={chip.value}
color={chip.color}
/>
<Tag className="w-fit" size="xs" type={chip.type}>
{chip.value}
</Tag>
)}
{deployment.url && (
<Typography variant="small" className="text-black" placeholder={''}>
<div className="flex flex-col gap-3">
{/* Title */}
<p className="text-sm font-medium text-elements-high-em tracking-[0.006em]">
{deployment.url}
</Typography>
)}
<Typography variant="small" placeholder={''}>
^ {deployment.branch} ^{' '}
{deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
{deployment.commitMessage}
</Typography>
<Typography variant="small" placeholder={''}>
^ {relativeTimeMs(deployment.createdAt)} ^{' '}
{formatAddress(deployment.createdBy.name ?? '')}
</Typography>
</Card>
</p>
{/* Branch & commit */}
<div className="flex items-center gap-6 text-elements-low-em">
<div className="flex items-center gap-2">
<BranchStrokeIcon size={16} />
<p className="text-sm tracking-[0.006em]">{deployment.branch}</p>
</div>
<div className="flex items-center gap-2 w-full">
<CommitIcon size={16} />
<p className="text-sm tracking-[0.006em] max-w-[67.5%] sm:max-w-[80%]">
<OverflownText content={commit}>{commit}</OverflownText>
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2 text-elements-low-em">
<ClockOutlineIcon size={16} />
<p className="text-sm tracking-[0.006em]">
{relativeTimeMs(deployment.createdAt)}
</p>
<Avatar
size={20}
type="orange"
initials={getInitials(deployment.createdBy.name ?? '')}
// TODO: Add avatar image URL
// imageSrc={deployment.createdBy.imageUrl}
/>
<p className="text-sm tracking-[0.006em]">
{deployment.createdBy.name ?? 'Unknown'}
</p>
</div>
</div>
);
};

View File

@ -17,12 +17,10 @@ import {
MenuList,
} from '@material-tailwind/react';
import { ComponentPropsWithRef } from 'react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import AssignDomainDialog from './AssignDomainDialog';
import DeploymentDialogBodyCard from './DeploymentDialogBodyCard';
import { Typography } from '@material-tailwind/react';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useGQLClient } from 'context/GQLClientContext';
import { cn } from 'utils/classnames';
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
deployment: Deployment;
@ -158,106 +156,44 @@ export const DeploymentMenu = ({
</Menu>
</div>
{/* Dialogs */}
<ConfirmDialog
<ChangeStateToProductionDialog
dialogTitle="Change to production?"
handleOpen={() => setChangeToProduction((preVal) => !preVal)}
open={changeToProduction}
confirmButtonTitle="Change"
color="blue"
handleCancel={() => setChangeToProduction((preVal) => !preVal)}
open={changeToProduction}
handleConfirm={async () => {
await updateDeployment();
setChangeToProduction((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small" placeholder={''}>
Upon confirmation, this deployment will be changed to production.
</Typography>
<DeploymentDialogBodyCard deployment={deployment} />
<Typography variant="small" placeholder={''}>
The new deployment will be associated with these domains:
</Typography>
{prodBranchDomains.length > 0 &&
prodBranchDomains.map((value) => {
return (
<Typography
variant="small"
color="blue"
key={value.id}
placeholder={''}
>
^ {value.name}
</Typography>
);
})}
</div>
</ConfirmDialog>
<ConfirmDialog
deployment={deployment}
domains={prodBranchDomains}
/>
<ChangeStateToProductionDialog
dialogTitle="Redeploy to production?"
handleOpen={() => setRedeployToProduction((preVal) => !preVal)}
handleCancel={() => setRedeployToProduction((preVal) => !preVal)}
open={redeployToProduction}
confirmButtonTitle="Redeploy"
color="blue"
handleConfirm={async () => {
await redeployToProd();
setRedeployToProduction((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small" placeholder={''}>
Upon confirmation, new deployment will be created with the same
source code as current deployment.
</Typography>
<DeploymentDialogBodyCard deployment={deployment} />
<Typography variant="small" placeholder={''}>
These domains will point to your new deployment:
</Typography>
{deployment.domain?.name && (
<Typography variant="small" color="blue" placeholder={''}>
{deployment.domain?.name}
</Typography>
)}
</div>
</ConfirmDialog>
deployment={deployment}
domains={deployment.domain ? [deployment.domain] : []}
/>
{Boolean(currentDeployment) && (
<ConfirmDialog
<ChangeStateToProductionDialog
dialogTitle="Rollback to this deployment?"
handleOpen={() => setRollbackDeployment((preVal) => !preVal)}
handleCancel={() => setRollbackDeployment((preVal) => !preVal)}
open={rollbackDeployment}
confirmButtonTitle="Rollback"
color="blue"
handleConfirm={async () => {
await rollbackDeploymentHandler();
setRollbackDeployment((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small" placeholder={''}>
Upon confirmation, this deployment will replace your current
deployment
</Typography>
<DeploymentDialogBodyCard
deployment={currentDeployment}
chip={{
value: 'Live Deployment',
color: 'green',
}}
/>
<DeploymentDialogBodyCard
deployment={deployment}
chip={{
value: 'New Deployment',
color: 'orange',
}}
/>
<Typography variant="small" placeholder={''}>
These domains will point to your new deployment:
</Typography>
<Typography variant="small" color="blue" placeholder={''}>
^ {currentDeployment.domain?.name}
</Typography>
</div>
</ConfirmDialog>
deployment={currentDeployment}
newDeployment={deployment}
domains={currentDeployment.domain ? [currentDeployment.domain] : []}
/>
)}
<AssignDomainDialog
open={assignDomainDialog}

View File

@ -12,9 +12,9 @@ import {
Card,
} from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
enum RefreshStatus {
IDLE,
@ -118,28 +118,16 @@ const DomainCard = ({
</Menu>
</div>
<ConfirmDialog
dialogTitle="Delete domain?"
handleOpen={() => setDeleteDialogOpen((preVal) => !preVal)}
<DeleteDomainDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
confirmButtonTitle="Yes, Delete domain"
handleConfirm={() => {
deleteDomain();
setDeleteDialogOpen((preVal) => !preVal);
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Once deleted, the project{' '}
<span className="bg-blue-100 rounded-sm p-0.5 text-blue-700">
{project.name}
</span>{' '}
will not be accessible from the domain{' '}
<span className="bg-blue-100 rounded-sm p-0.5 text-blue-700">
{domain.name}.
</span>
</Typography>
</ConfirmDialog>
projectName={project.name}
domainName={domain.name}
/>
</div>
<Typography variant="small" placeholder={''}>

View File

@ -5,8 +5,8 @@ import { EnvironmentVariable } from 'gql-client';
import { IconButton, Input, Typography } from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { useGQLClient } from '../../../../context/GQLClientContext';
import { useGQLClient } from 'context/GQLClientContext';
import { DeleteVariableDialog } from 'components/projects/Dialog/DeleteVariableDialog';
const ShowPasswordIcon = ({
handler,
@ -161,20 +161,12 @@ const EditEnvironmentVariableRow = ({
</>
)}
</div>
<ConfirmDialog
dialogTitle="Delete variable"
handleOpen={() => setDeleteDialogOpen((preVal) => !preVal)}
<DeleteVariableDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
confirmButtonTitle="Yes, Confirm delete"
handleConfirm={removeEnvironmentVariableHandler}
color="red"
>
<Typography variant="small" placeholder={''}>
Are you sure you want to delete the variable&nbsp;
<span className="bg-blue-100">{variable.key}</span>?
</Typography>
</ConfirmDialog>
variableKey={variable.key}
/>
</>
);
};

View File

@ -3,15 +3,14 @@ import { Permission, User } from 'gql-client';
import {
Select,
Typography,
Option,
Chip,
IconButton,
Tooltip,
} from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { formatAddress } from '../../../../utils/format';
import { formatAddress } from 'utils/format';
import { RemoveMemberDialog } from 'components/projects/Dialog/RemoveMemberDialog';
const PERMISSION_OPTIONS = [
{
@ -141,25 +140,19 @@ const MemberCard = ({
</div>
)}
</div>
<ConfirmDialog
dialogTitle="Remove member?"
handleOpen={() => setRemoveMemberDialogOpen((preVal) => !preVal)}
<RemoveMemberDialog
handleCancel={() => setRemoveMemberDialogOpen((preVal) => !preVal)}
open={removeMemberDialogOpen}
confirmButtonTitle="Yes, Remove member"
handleConfirm={() => {
setRemoveMemberDialogOpen((preVal) => !preVal);
if (onRemoveProjectMember) {
onRemoveProjectMember();
}
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Once removed, {formatAddress(member.name ?? '')} (
{formatAddress(ethAddress)}@{emailDomain}) will not be able to access
this project.
</Typography>
</ConfirmDialog>
memberName={member.name ?? ''}
ethAddress={ethAddress}
emailDomain={emailDomain}
/>
</div>
);
};

View File

@ -2,8 +2,8 @@ import React, { useState } from 'react';
import { Button, Typography } from '@material-tailwind/react';
import { GitRepositoryDetails } from '../../../../types';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { GitRepositoryDetails } from 'types';
import { DisconnectRepositoryDialog } from 'components/projects/Dialog/DisconnectRepositoryDialog';
const RepoConnectedSection = ({
linkedRepo,
@ -34,21 +34,13 @@ const RepoConnectedSection = ({
^ Disconnect
</Button>
</div>
<ConfirmDialog
dialogTitle="Disconnect repository?"
handleOpen={() => setDisconnectRepoDialogOpen((preVal) => !preVal)}
<DisconnectRepositoryDialog
handleCancel={() => setDisconnectRepoDialogOpen((preVal) => !preVal)}
open={disconnectRepoDialogOpen}
confirmButtonTitle="Yes, confirm disconnect"
handleConfirm={() => {
setDisconnectRepoDialogOpen((preVal) => !preVal);
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Any data tied to your Git project may become misconfigured. Are you
sure you want to continue?
</Typography>
</ConfirmDialog>
/>
</div>
);
};

View File

@ -1,9 +1,9 @@
import React, { useState } from 'react';
import toast from 'react-hot-toast';
import { Button, Typography } from '@material-tailwind/react';
import { Button } from '@material-tailwind/react';
import ConfirmDialog from '../../../shared/ConfirmDialog';
import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog';
interface WebhookCardProps {
webhookUrl: string;
@ -15,7 +15,6 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
return (
<div className="flex justify-between w-full mb-3">
{webhookUrl}
<div className="flex gap-3">
<Button
size="sm"
@ -38,23 +37,15 @@ const WebhookCard = ({ webhookUrl, onDelete }: WebhookCardProps) => {
X
</Button>
</div>
<ConfirmDialog
dialogTitle="Delete webhook"
handleOpen={() => setDeleteDialogOpen((preVal) => !preVal)}
<DeleteWebhookDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
confirmButtonTitle="Yes, Confirm delete"
handleConfirm={() => {
setDeleteDialogOpen((preVal) => !preVal);
onDelete();
}}
color="red"
>
<Typography variant="small" placeholder={''}>
Are you sure you want to delete the variable{' '}
<span className="bg-blue-100 p-0.5 rounded-sm">{webhookUrl}</span>?
</Typography>
</ConfirmDialog>
webhookUrl={webhookUrl}
/>
</div>
);
};

View File

@ -1,64 +1,52 @@
import React from 'react';
import React, { ReactNode } from 'react';
import { Modal, ModalProps } from './Modal';
import { Button, ButtonOrLinkProps } from './Button';
import { color } from '@material-tailwind/react/types/components/button';
import {
Typography,
Button,
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
} from '@material-tailwind/react';
type ConfirmDialogProp = {
children: React.ReactNode;
dialogTitle: string;
export type ConfirmDialogProps = ModalProps & {
children?: ReactNode;
dialogTitle?: string;
open: boolean;
handleOpen: () => void;
confirmButtonTitle: string;
handleCancel: () => void;
confirmButtonTitle?: string;
handleConfirm?: () => void;
color: color;
cancelButtonProps?: ButtonOrLinkProps;
confirmButtonProps?: ButtonOrLinkProps;
};
const ConfirmDialog = ({
children,
dialogTitle,
open,
handleOpen,
handleCancel,
confirmButtonTitle,
handleConfirm,
color,
}: ConfirmDialogProp) => {
cancelButtonProps,
confirmButtonProps,
...props
}: ConfirmDialogProps) => {
// Close the dialog when the user clicks outside of it
const handleOpenChange = (open: boolean) => {
if (!open) return handleCancel?.();
};
return (
<Dialog open={open} handler={handleOpen} placeholder={''}>
<DialogHeader className="flex justify-between" placeholder={''}>
<Typography variant="h6" placeholder={''}>
{dialogTitle}{' '}
</Typography>
<Button
variant="outlined"
onClick={handleOpen}
className=" rounded-full"
placeholder={''}
>
X
</Button>
</DialogHeader>
<DialogBody placeholder={''}>{children}</DialogBody>
<DialogFooter className="flex justify-start gap-2" placeholder={''}>
<Button variant="outlined" onClick={handleOpen} placeholder={''}>
Cancel
</Button>
<Button
variant="gradient"
color={color}
onClick={handleConfirm}
placeholder={''}
>
{confirmButtonTitle}
</Button>
</DialogFooter>
</Dialog>
<Modal {...props} onOpenChange={handleOpenChange}>
<Modal.Content>
<Modal.Header>{dialogTitle}</Modal.Header>
<Modal.Body>{children}</Modal.Body>
<Modal.Footer>
<Button
variant="tertiary"
onClick={handleCancel}
{...cancelButtonProps}
>
Cancel
</Button>
<Button onClick={handleConfirm} {...confirmButtonProps}>
{confirmButtonTitle}
</Button>
</Modal.Footer>
</Modal.Content>
</Modal>
);
};

View File

@ -0,0 +1,20 @@
import React from 'react';
import { CustomIcon, CustomIconProps } from './CustomIcon';
export const ChevronDoubleDownIcon = (props: CustomIconProps) => {
return (
<CustomIcon
width="20"
height="21"
viewBox="0 0 20 21"
fill="none"
{...props}
>
<path
d="M6.66699 12.0174L10.0003 15.3507L13.3337 12.0174M6.66699 6.18408L10.0003 9.51742L13.3337 6.18408"
stroke="currentColor"
strokeLinecap="square"
/>
</CustomIcon>
);
};

View File

@ -61,6 +61,7 @@ export * from './CirclePlaceholderOnIcon';
export * from './WarningTriangleIcon';
export * from './CheckRadioOutlineIcon';
export * from './TrendingIcon';
export * from './ChevronDoubleDownIcon';
// Templates
export * from './templates';

View File

@ -0,0 +1,73 @@
import type { VariantProps } from 'tailwind-variants';
import { tv } from 'tailwind-variants';
export const modalTheme = tv({
slots: {
overlay: [
'z-modal',
'fixed',
'inset-0',
'bg-black/80',
'backdrop-blur-sm',
'overflow-y-auto',
'flex',
'justify-center',
'items-end',
'sm:items-center',
'p-0',
'sm:p-10',
'data-[state=closed]:animate-[dialog-overlay-hide_200ms]',
'data-[state=open]:animate-[dialog-overlay-show_200ms]',
'data-[state=closed]:hidden', // Fix overlay not close when modal is closed
],
close: ['absolute', 'right-4', 'top-2', 'sm:right-6', 'sm:top-3', 'z-[1]'],
header: [
'flex',
'flex-col',
'gap-4',
'items-start',
'px-4',
'py-4',
'sm:px-6',
'sm:py-5',
'bg-base-bg-alternate',
],
headerTitle: [
'text-base',
'sm:text-lg',
'tracking-[0.011em]',
'sm:tracking-normal',
'text-elements-high-em',
],
headerDescription: ['text-sm', 'text-elements-low-em'],
footer: ['flex', 'gap-3', 'px-4', 'pb-4', 'pt-7', 'sm:pb-6', 'sm:px-6'],
content: [
'h-fit',
'sm:min-h-0',
'sm:m-auto',
'relative',
'flex',
'flex-col',
'overflow-hidden',
'w-full',
'sm:max-w-[562px]',
'rounded-2xl',
'bg-base-bg',
'shadow-card',
'text-elements-high-em',
],
body: ['flex-1', 'px-4', 'pt-4', 'sm:pt-6', 'sm:px-6'],
},
variants: {
fullPage: {
true: {
content: ['h-full'],
overlay: ['!p-0'],
},
},
},
defaultVariants: {
fullPage: false,
},
});
export type ModalVariants = VariantProps<typeof modalTheme>;

View File

@ -0,0 +1,46 @@
import React from 'react';
import type { DialogProps } from '@radix-ui/react-dialog';
import { Root, Trigger } from '@radix-ui/react-dialog';
import type { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
import type { ModalVariants } from './Modal.theme';
import { ModalBody } from './ModalBody';
import { ModalContent } from './ModalContent';
import { ModalFooter } from './ModalFooter';
import { ModalHeader } from './ModalHeader';
import ModalProvider from './ModalProvider';
export interface ModalProps
extends ComponentPropsWithoutRef<'div'>,
ModalVariants,
DialogProps {
hasCloseButton?: boolean;
hasOverlay?: boolean;
preventClickOutsideToClose?: boolean;
}
export const Modal = ({
children,
hasCloseButton = true,
hasOverlay = true,
preventClickOutsideToClose = false,
fullPage = false,
...props
}: PropsWithChildren<ModalProps>) => {
return (
<ModalProvider
fullPage={fullPage}
hasCloseButton={hasCloseButton}
hasOverlay={hasOverlay}
preventClickOutsideToClose={preventClickOutsideToClose}
{...props}
>
<Root {...props}>{children}</Root>
</ModalProvider>
);
};
Modal.Trigger = Trigger;
Modal.Content = ModalContent;
Modal.Header = ModalHeader;
Modal.Footer = ModalFooter;
Modal.Body = ModalBody;

View File

@ -0,0 +1,26 @@
import React from 'react';
import type { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
import { modalTheme } from 'components/shared/Modal/Modal.theme';
export interface ModalBodyProps extends ComponentPropsWithoutRef<'div'> {
className?: string;
}
export const ModalBody = ({
children,
className,
...props
}: PropsWithChildren<ModalBodyProps>) => {
const { body } = modalTheme();
return (
<div
className={body({
className,
})}
{...props}
>
{children}
</div>
);
};

View File

@ -0,0 +1 @@
export * from './ModalBody';

View File

@ -0,0 +1,64 @@
import React from 'react';
import type { DialogContentProps } from '@radix-ui/react-dialog';
import { Close, Content, Overlay, Portal } from '@radix-ui/react-dialog';
import { Ref, forwardRef, type PropsWithChildren } from 'react';
import { useModal } from 'components/shared/Modal/ModalProvider';
import { modalTheme } from 'components/shared/Modal/Modal.theme';
import { Button } from 'components/shared/Button';
import { CrossIcon } from 'components/shared/CustomIcon';
type PointerDownOutsideEvent = CustomEvent<{
originalEvent: PointerEvent;
}>;
export interface ModalContentProps extends DialogContentProps {
className?: string;
}
const ModalContent = forwardRef(
(
{ children, className, ...props }: PropsWithChildren<ModalContentProps>,
forwardedRef,
) => {
const { hasCloseButton, preventClickOutsideToClose, fullPage } = useModal();
const { content, close, overlay } = modalTheme({ fullPage });
const preventClickOutsideToCloseProps = preventClickOutsideToClose && {
onPointerDownOutside: (e: PointerDownOutsideEvent) => e.preventDefault(),
onEscapeKeyDown: (e: KeyboardEvent) => e.preventDefault(),
};
return (
<Portal>
<Overlay className={overlay({ fullPage })}>
<Content
className={content({ className, fullPage })}
{...preventClickOutsideToCloseProps}
{...props}
ref={forwardedRef as Ref<HTMLDivElement>}
>
{hasCloseButton && (
<Close asChild>
<Button
aria-label="Close"
className={close()}
size="sm"
iconOnly
variant="tertiary"
>
<CrossIcon />
</Button>
</Close>
)}
{children}
</Content>
</Overlay>
</Portal>
);
},
);
ModalContent.displayName = 'ModalContent';
export { ModalContent };

View File

@ -0,0 +1 @@
export * from './ModalContent';

View File

@ -0,0 +1,23 @@
import React from 'react';
import type { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
import { modalTheme } from 'components/shared/Modal/Modal.theme';
type ModalFooterProps = ComponentPropsWithoutRef<'div'> & {
className?: string;
};
export const ModalFooter = ({
children,
className,
...props
}: PropsWithChildren<ModalFooterProps>) => {
const { footer } = modalTheme({
className,
});
return (
<footer className={footer({ className })} {...props}>
{children}
</footer>
);
};

View File

@ -0,0 +1 @@
export * from './ModalFooter';

View File

@ -0,0 +1,56 @@
import React from 'react';
import type { DialogDescriptionProps } from '@radix-ui/react-dialog';
import { Description, Title } from '@radix-ui/react-dialog';
import type { ComponentPropsWithoutRef, PropsWithChildren } from 'react';
import { Heading } from 'components/shared/Heading';
import { modalTheme } from 'components/shared/Modal/Modal.theme';
import { WavyBorder } from 'components/shared/WavyBorder';
type ModalHeaderProps = ComponentPropsWithoutRef<'div'> & {
className?: string;
description?: string | React.ReactNode;
descriptionProps?: DialogDescriptionProps;
headingProps?: ComponentPropsWithoutRef<'h2'>;
};
export const ModalHeader = ({
children,
description,
className,
descriptionProps,
headingProps,
...props
}: PropsWithChildren<ModalHeaderProps>) => {
const { header, headerDescription, headerTitle } = modalTheme();
return (
<>
<div
className={header({
className,
})}
{...props}
>
<Title asChild>
<Heading
{...headingProps}
className={headerTitle({ className: headingProps?.className })}
>
{children}
</Heading>
</Title>
{description && (
<Description
{...descriptionProps}
className={headerDescription({
className: descriptionProps?.className,
})}
>
{description}
</Description>
)}
</div>
<WavyBorder className="-mt-0.5" variant="stroke" />
</>
);
};

View File

@ -0,0 +1 @@
export * from './ModalHeader';

View File

@ -0,0 +1,42 @@
import React from 'react';
import type { PropsWithChildren } from 'react';
import { createContext, useContext } from 'react';
import type { ModalProps } from './Modal';
import type { ModalVariants } from './Modal.theme';
export interface ModalProviderProps
extends Partial<ModalVariants>,
ModalProps {}
type ModalProviderContext = ReturnType<typeof useModalValues>;
const ModalContext = createContext<Partial<ModalProviderContext> | undefined>(
undefined,
);
// For inferring return type
const useModalValues = (props: ModalProviderProps) => {
return props;
};
export const ModalProvider = ({
children,
...props
}: PropsWithChildren<ModalProviderProps>): JSX.Element => {
const values = useModalValues(props);
return (
<ModalContext.Provider value={values}>{children}</ModalContext.Provider>
);
};
export const useModal = () => {
const context = useContext(ModalContext);
if (context === undefined) {
throw new Error('useModal was used outside of its Provider');
}
return context;
};
export default ModalProvider;

View File

@ -0,0 +1 @@
export * from './Modal';

View File

@ -6,7 +6,7 @@ import React, {
import { tagTheme, type TagTheme } from './Tag.theme';
import { cloneIcon } from 'utils/cloneIcon';
type TagProps = ComponentPropsWithoutRef<'div'> &
export type TagProps = ComponentPropsWithoutRef<'div'> &
TagTheme & {
/**
* The optional left icon element for a component.

View File

@ -153,4 +153,44 @@
.focus-ring {
@apply focus-visible:ring-[3px] focus-visible:ring-snowball-200 focus-visible:ring-offset-1 focus-visible:ring-offset-snowball-500 focus-visible:outline-none;
}
@keyframes dialog-overlay-show {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes dialog-overlay-hide {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes dialog-content-show {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.95);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
@keyframes dialog-content-hide {
from {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.95);
}
}
}

View File

@ -72,7 +72,7 @@ const ProjectSearch = () => {
</div>
{/* Content */}
<section className="h-full z-0">
<section className="h-full z-0 overflow-y-auto">
<Outlet />
</section>
</section>

View File

@ -35,6 +35,8 @@ import {
import { renderDefaultTag, renderMinimalTag } from './renders/tag';
import { renderToast, renderToastsWithCta } from './renders/toast';
import { renderTooltips } from './renders/tooltip';
import { Button } from 'components/shared/Button';
import { Modal } from 'components/shared/Modal';
const Page: React.FC = () => {
const [singleDate, setSingleDate] = useState<Value>();
@ -57,6 +59,32 @@ const Page: React.FC = () => {
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Modal */}
<div className="flex flex-col gap-10 items-center justify-between">
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Modal</h1>
<div className="flex gap-4 flex-wrap items-center justify-center">
{/* Modal example */}
<Modal>
<Modal.Trigger asChild>
<Button>Open modal</Button>
</Modal.Trigger>
<Modal.Content>
<Modal.Header>Modal title</Modal.Header>
<Modal.Body>
<p>Modal content</p>
</Modal.Body>
<Modal.Footer>
<Button>Close</Button>
</Modal.Footer>
</Modal.Content>
</Modal>
</div>
</div>
</div>
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Steps */}
<div className="flex flex-col gap-10 items-center justify-between">
<div className="flex flex-col gap-10 items-center justify-between">

View File

@ -0,0 +1,274 @@
import React from 'react';
import { useState } from 'react';
import { Button } from 'components/shared/Button';
import { Modal } from 'components/shared/Modal';
import { TransferProjectDialog } from 'components/projects/Dialog/TransferProjectDialog';
import { DeleteWebhookDialog } from 'components/projects/Dialog/DeleteWebhookDialog';
import { DisconnectRepositoryDialog } from 'components/projects/Dialog/DisconnectRepositoryDialog';
import { RemoveMemberDialog } from 'components/projects/Dialog/RemoveMemberDialog';
import { DeleteVariableDialog } from 'components/projects/Dialog/DeleteVariableDialog';
import { DeleteDomainDialog } from 'components/projects/Dialog/DeleteDomainDialog';
import { CancelDeploymentDialog } from 'components/projects/Dialog/CancelDeploymentDialog';
import {
Deployment,
DeploymentStatus,
Domain,
DomainStatus,
Environment,
} from 'gql-client';
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
const deployment: Deployment = {
id: '1',
domain: {
id: 'domain1',
branch: 'main',
name: 'example.com',
status: DomainStatus.Live,
redirectTo: null,
createdAt: '1677609600', // 2023-02-25T12:00:00Z
updatedAt: '1677613200', // 2023-02-25T13:00:00Z
},
branch: 'main',
commitHash: 'a1b2c3d',
commitMessage:
'lkajsdlakjsdlaijwlkjadlksjdlaisjdlakjswdalijsdlaksdj lakjsdlasjdlaijwdel akjsdlaj sldkjaliwjdeal ksjdla ijsdlaksjd',
url: 'https://deploy1.example.com',
environment: Environment.Production,
isCurrent: true,
status: DeploymentStatus.Ready,
createdBy: {
id: 'user1',
name: 'Alice',
email: 'alice@example.com',
isVerified: true,
createdAt: '1672656000', // 2023-01-01T10:00:00Z
updatedAt: '1672659600', // 2023-01-01T11:00:00Z
gitHubToken: null,
},
createdAt: '1677676800', // 2023-03-01T12:00:00Z
updatedAt: '1677680400', // 2023-03-01T13:00:00Z
};
const domains: Domain[] = [
{
id: '1',
branch: 'main',
name: 'saugat.com',
status: DomainStatus.Live,
redirectTo: null,
createdAt: '1677676800', // 2023-03-01T12:00:00Z
updatedAt: '1677680400', // 2023-03-01T13:00:00Z
},
{
id: '2',
branch: 'main',
name: 'www.saugat.com',
status: DomainStatus.Live,
redirectTo: null,
createdAt: '1677676800', // 2023-03-01T12:00:00Z
updatedAt: '1677680400', // 2023-03-01T13:00:00Z
},
];
const ModalsPage: React.FC = () => {
const [openTransferDialog, setOpenTransferDialog] = useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [disconnectRepoDialogOpen, setDisconnectRepoDialogOpen] =
useState(false);
const [removeMemberDialogOpen, setRemoveMemberDialogOpen] = useState(false);
const [deleteVariableDialogOpen, setDeleteVariableDialogOpen] =
useState(false);
const [deleteDomainDialogOpen, setDeleteDomainDialogOpen] = useState(false);
const [cancelDeploymentDialogOpen, setCancelDeploymentDialogOpen] =
useState(false);
const [changeProductionDialogOpen, setChangeProductionDialogOpen] =
useState(false);
const [redeployToProduction, setRedeployToProduction] = useState(false);
const [rollbackDeployment, setRollbackDeployment] = useState(false);
return (
<div className="relative h-full min-h-full">
<div className="flex flex-col items-center justify-center container mx-auto px-20 py-20">
<h1 className="text-4xl font-bold">Manual Storybook</h1>
<p className="mt-4 text-lg text-center text-gray-500">
Get started by editing{' '}
<code className="p-2 font-mono text-sm bg-gray-100 rounded-md">
packages/frontend/src/pages/components/index.tsx
</code>
</p>
<div className="w-full h border border-gray-200 px-20 my-10" />
{/* Modal */}
<div className="flex flex-col gap-10 items-center justify-between">
<div className="flex flex-col gap-10 items-center justify-between">
<h1 className="text-2xl font-bold">Modal</h1>
<div className="flex gap-4 flex-wrap items-center justify-center">
{/* Modal example */}
<Modal>
<Modal.Trigger asChild>
<Button>Open modal</Button>
</Modal.Trigger>
<Modal.Content>
<Modal.Header>Modal title</Modal.Header>
<Modal.Body>
<p>Modal content</p>
</Modal.Body>
<Modal.Footer>
<Button>Close</Button>
</Modal.Footer>
</Modal.Content>
</Modal>
{/* Transfer project */}
<Button onClick={() => setOpenTransferDialog(true)}>
Transfer project
</Button>
<TransferProjectDialog
handleCancel={() => setOpenTransferDialog(!openTransferDialog)}
open={openTransferDialog}
handleConfirm={() => setOpenTransferDialog(!openTransferDialog)}
projectName="nextjs-boilerplate"
from="ayungavis"
to="Airfoil"
/>
{/* Delete webhook */}
<Button onClick={() => setDeleteDialogOpen(true)}>
Delete webhook
</Button>
<DeleteWebhookDialog
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
handleConfirm={() => setDeleteDialogOpen((preVal) => !preVal)}
webhookUrl="examplehook.com"
/>
{/* Disconnect repository */}
<Button onClick={() => setDisconnectRepoDialogOpen(true)}>
Disconnect repository
</Button>
<DisconnectRepositoryDialog
handleCancel={() =>
setDisconnectRepoDialogOpen((preVal) => !preVal)
}
open={disconnectRepoDialogOpen}
handleConfirm={() => {
setDisconnectRepoDialogOpen((preVal) => !preVal);
}}
/>
{/* Remove member */}
<Button onClick={() => setRemoveMemberDialogOpen(true)}>
Remove member
</Button>
<RemoveMemberDialog
dialogTitle="Remove member?"
handleCancel={() =>
setRemoveMemberDialogOpen((preVal) => !preVal)
}
open={removeMemberDialogOpen}
confirmButtonTitle="Yes, Remove member"
handleConfirm={() =>
setRemoveMemberDialogOpen((preVal) => !preVal)
}
memberName="John Doe"
ethAddress="0x1234567890"
emailDomain="example.com"
/>
{/* Delete variable */}
<Button onClick={() => setDeleteVariableDialogOpen(true)}>
Delete variable
</Button>
<DeleteVariableDialog
handleCancel={() =>
setDeleteVariableDialogOpen((preVal) => !preVal)
}
open={deleteVariableDialogOpen}
handleConfirm={() =>
setDeleteVariableDialogOpen((preVal) => !preVal)
}
variableKey="AIUTH_TOKEN"
/>
{/* Delete domain */}
<Button onClick={() => setDeleteDomainDialogOpen(true)}>
Delete domain
</Button>
<DeleteDomainDialog
handleCancel={() =>
setDeleteDomainDialogOpen((preVal) => !preVal)
}
open={deleteDomainDialogOpen}
handleConfirm={() =>
setDeleteDomainDialogOpen((preVal) => !preVal)
}
projectName="Airfoil"
domainName="airfoil.com"
/>
{/* Cancel deployment */}
<Button onClick={() => setCancelDeploymentDialogOpen(true)}>
Cancel deployment
</Button>
<CancelDeploymentDialog
handleCancel={() =>
setCancelDeploymentDialogOpen(!cancelDeploymentDialogOpen)
}
open={cancelDeploymentDialogOpen}
handleConfirm={() =>
setCancelDeploymentDialogOpen(!cancelDeploymentDialogOpen)
}
/>
{/* Change to production */}
<Button onClick={() => setChangeProductionDialogOpen(true)}>
Change to production
</Button>
<ChangeStateToProductionDialog
dialogTitle="Change to production?"
confirmButtonTitle="Change"
handleCancel={() => setChangeProductionDialogOpen(false)}
open={changeProductionDialogOpen}
handleConfirm={() => setChangeProductionDialogOpen(false)}
deployment={deployment}
domains={domains}
/>
{/* Redeploy to production */}
<Button onClick={() => setRedeployToProduction(true)}>
Redeploy to production
</Button>
<ChangeStateToProductionDialog
dialogTitle="Redeploy to production?"
handleCancel={() =>
setRedeployToProduction((preVal) => !preVal)
}
open={redeployToProduction}
confirmButtonTitle="Redeploy"
handleConfirm={async () =>
setRedeployToProduction((preVal) => !preVal)
}
deployment={deployment}
domains={deployment.domain ? [deployment.domain] : []}
/>
{/* Rollback to this deployment */}
<Button onClick={() => setRollbackDeployment(true)}>
Rollback to this deployment
</Button>
<ChangeStateToProductionDialog
dialogTitle="Rollback to this deployment?"
handleCancel={() => setRollbackDeployment((preVal) => !preVal)}
open={rollbackDeployment}
confirmButtonTitle="Rollback"
handleConfirm={async () =>
setRollbackDeployment((preVal) => !preVal)
}
deployment={deployment}
newDeployment={deployment}
domains={deployment.domain ? [deployment.domain] : []}
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default ModalsPage;

View File

@ -6,11 +6,11 @@ import { Organization } from 'gql-client';
import { Button, Typography, Input, Option } from '@material-tailwind/react';
import DeleteProjectDialog from '../../../../../components/projects/project/settings/DeleteProjectDialog';
import ConfirmDialog from '../../../../../components/shared/ConfirmDialog';
import { useGQLClient } from '../../../../../context/GQLClientContext';
import AsyncSelect from '../../../../../components/shared/AsyncSelect';
import { OutletContextType } from '../../../../../types';
import DeleteProjectDialog from 'components/projects/project/settings/DeleteProjectDialog';
import { useGQLClient } from 'context/GQLClientContext';
import AsyncSelect from 'components/shared/AsyncSelect';
import { OutletContextType } from 'types';
import { TransferProjectDialog } from 'components/projects/Dialog/TransferProjectDialog';
const CopyIcon = ({ value }: { value: string }) => {
return (
@ -230,19 +230,14 @@ const GeneralTabPanel = () => {
Transfer
</Button>
</form>
<ConfirmDialog
dialogTitle="Transfer project"
handleOpen={() => setOpenTransferDialog(!openTransferDialog)}
<TransferProjectDialog
handleCancel={() => setOpenTransferDialog(!openTransferDialog)}
open={openTransferDialog}
confirmButtonTitle="Yes, Confirm transfer"
handleConfirm={handleTransferProject}
color="blue"
>
<Typography variant="small" placeholder={''}>
Upon confirmation, your project {project.name} will be transferred
from {project.organization.name} to {selectedUserOrgName}.
</Typography>
</ConfirmDialog>
projectName={project.name}
from={project.organization.name}
to={selectedUserOrgName}
/>
</div>
<div className="mb-1">
<Typography variant="h6" placeholder={''}>