diff --git a/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx b/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx
new file mode 100644
index 0000000..f238788
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/CancelDeploymentDialog.tsx
@@ -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 (
+
+
+ This will halt the deployment and you'll have to start the process
+ from scratch.
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx b/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx
new file mode 100644
index 0000000..cf7f1ef
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/ChangeStateToProductionDialog.tsx
@@ -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 (
+
+
+
+
+ Upon confirmation, this deployment will be changed to production.
+
+
+ {newDeployment && (
+ <>
+
+ {Array.from({ length: 7 }).map((_, index) => (
+
+ ))}
+
+
+ >
+ )}
+
+
+
+ The new deployment will be associated with these domains:
+
+ {domains.length > 0 &&
+ domains.map((value) => {
+ return (
+
}
+ variant="link"
+ key={value.id}
+ >
+ {value.name}
+
+ );
+ })}
+
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx
new file mode 100644
index 0000000..5d6d3e0
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/DeleteDomainDialog.tsx
@@ -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 (
+
+
+ Once deleted, the project{' '}
+
+ {projectName}
+ {' '}
+ will not be accessible from the domain{' '}
+
+ {domainName}
+
+ .
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx
new file mode 100644
index 0000000..227fa34
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/DeleteVariableDialog.tsx
@@ -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 (
+
+
+ Are you sure you want to delete the variable{' '}
+
+ {variableKey}
+
+ ?
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx b/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx
new file mode 100644
index 0000000..c2c82c1
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/DeleteWebhookDialog.tsx
@@ -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 (
+
+
+ Are you sure you want to delete{' '}
+
+ {webhookUrl}
+
+ ?
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx b/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx
new file mode 100644
index 0000000..52ac52f
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/DisconnectRepositoryDialog.tsx
@@ -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 (
+
+
+ Any data tied to your Git project may become misconfigured. Are you sure
+ you want to continue?
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx b/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx
new file mode 100644
index 0000000..6695ff8
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/RemoveMemberDialog.tsx
@@ -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 (
+
+
+ Once removed, {formatAddress(memberName)} ({formatAddress(ethAddress)}@
+ {emailDomain}) will not be able to access this project.
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx b/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx
new file mode 100644
index 0000000..7d26ab9
--- /dev/null
+++ b/packages/frontend/src/components/projects/Dialog/TransferProjectDialog.tsx
@@ -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 (
+
+
+ Upon confirmation, your project{' '}
+
+ {projectName}
+ {' '}
+ will be transferred from{' '}
+
+ {from}
+ {' '}
+ to{' '}
+
+ {to}
+
+ .
+
+
+ );
+};
diff --git a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx
index e49b44d..0f5e117 100644
--- a/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx
+++ b/packages/frontend/src/components/projects/ProjectSearchBar/ProjectSearchBarDialog.tsx
@@ -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;
}
diff --git a/packages/frontend/src/components/projects/create/Deploy.tsx b/packages/frontend/src/components/projects/create/Deploy.tsx
index a251773..f443d5e 100644
--- a/packages/frontend/src/components/projects/create/Deploy.tsx
+++ b/packages/frontend/src/components/projects/create/Deploy.tsx
@@ -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
-
-
- This will halt the deployment and you will have to start the process
- from scratch.
-
-
+ />
diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx
index a8b40b8..fd008de 100644
--- a/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx
+++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDialogBodyCard.tsx
@@ -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 (
-
+
{chip && (
-
+
+ {chip.value}
+
)}
- {deployment.url && (
-
+
+ {/* Title */}
+
{deployment.url}
-
- )}
-
- ^ {deployment.branch} ^{' '}
- {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
- {deployment.commitMessage}
-
-
- ^ {relativeTimeMs(deployment.createdAt)} ^{' '}
- {formatAddress(deployment.createdBy.name ?? '')}
-
-
+
+ {/* Branch & commit */}
+
+
+
+
{deployment.branch}
+
+
+
+
+
+
+
+ {relativeTimeMs(deployment.createdAt)}
+
+
+
+ {deployment.createdBy.name ?? 'Unknown'}
+
+
+
);
};
diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentMenu.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentMenu.tsx
index f4eb980..516b468 100644
--- a/packages/frontend/src/components/projects/project/deployments/DeploymentMenu.tsx
+++ b/packages/frontend/src/components/projects/project/deployments/DeploymentMenu.tsx
@@ -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 = ({
{/* Dialogs */}
- setChangeToProduction((preVal) => !preVal)}
- open={changeToProduction}
confirmButtonTitle="Change"
- color="blue"
+ handleCancel={() => setChangeToProduction((preVal) => !preVal)}
+ open={changeToProduction}
handleConfirm={async () => {
await updateDeployment();
setChangeToProduction((preVal) => !preVal);
}}
- >
-
-
- Upon confirmation, this deployment will be changed to production.
-
-
-
- The new deployment will be associated with these domains:
-
- {prodBranchDomains.length > 0 &&
- prodBranchDomains.map((value) => {
- return (
-
- ^ {value.name}
-
- );
- })}
-
-
-
+ setRedeployToProduction((preVal) => !preVal)}
+ handleCancel={() => setRedeployToProduction((preVal) => !preVal)}
open={redeployToProduction}
confirmButtonTitle="Redeploy"
- color="blue"
handleConfirm={async () => {
await redeployToProd();
setRedeployToProduction((preVal) => !preVal);
}}
- >
-
-
- Upon confirmation, new deployment will be created with the same
- source code as current deployment.
-
-
-
- These domains will point to your new deployment:
-
- {deployment.domain?.name && (
-
- {deployment.domain?.name}
-
- )}
-
-
+ deployment={deployment}
+ domains={deployment.domain ? [deployment.domain] : []}
+ />
{Boolean(currentDeployment) && (
- setRollbackDeployment((preVal) => !preVal)}
+ handleCancel={() => setRollbackDeployment((preVal) => !preVal)}
open={rollbackDeployment}
confirmButtonTitle="Rollback"
- color="blue"
handleConfirm={async () => {
await rollbackDeploymentHandler();
setRollbackDeployment((preVal) => !preVal);
}}
- >
-
-
- Upon confirmation, this deployment will replace your current
- deployment
-
-
-
-
- These domains will point to your new deployment:
-
-
- ^ {currentDeployment.domain?.name}
-
-
-
+ deployment={currentDeployment}
+ newDeployment={deployment}
+ domains={currentDeployment.domain ? [currentDeployment.domain] : []}
+ />
)}
- setDeleteDialogOpen((preVal) => !preVal)}
+ setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
- confirmButtonTitle="Yes, Delete domain"
handleConfirm={() => {
deleteDomain();
setDeleteDialogOpen((preVal) => !preVal);
}}
- color="red"
- >
-
- Once deleted, the project{' '}
-
- {project.name}
- {' '}
- will not be accessible from the domain{' '}
-
- {domain.name}.
-
-
-
+ projectName={project.name}
+ domainName={domain.name}
+ />
diff --git a/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx b/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx
index c15c142..0e38298 100644
--- a/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx
+++ b/packages/frontend/src/components/projects/project/settings/EditEnvironmentVariableRow.tsx
@@ -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 = ({
>
)}
-
- setDeleteDialogOpen((preVal) => !preVal)}
+ setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
- confirmButtonTitle="Yes, Confirm delete"
handleConfirm={removeEnvironmentVariableHandler}
- color="red"
- >
-
- Are you sure you want to delete the variable
- {variable.key} ?
-
-
+ variableKey={variable.key}
+ />
>
);
};
diff --git a/packages/frontend/src/components/projects/project/settings/MemberCard.tsx b/packages/frontend/src/components/projects/project/settings/MemberCard.tsx
index 715d8bf..8dd8fd1 100644
--- a/packages/frontend/src/components/projects/project/settings/MemberCard.tsx
+++ b/packages/frontend/src/components/projects/project/settings/MemberCard.tsx
@@ -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 = ({
)}
- setRemoveMemberDialogOpen((preVal) => !preVal)}
+ setRemoveMemberDialogOpen((preVal) => !preVal)}
open={removeMemberDialogOpen}
- confirmButtonTitle="Yes, Remove member"
handleConfirm={() => {
setRemoveMemberDialogOpen((preVal) => !preVal);
if (onRemoveProjectMember) {
onRemoveProjectMember();
}
}}
- color="red"
- >
-
- Once removed, {formatAddress(member.name ?? '')} (
- {formatAddress(ethAddress)}@{emailDomain}) will not be able to access
- this project.
-
-
+ memberName={member.name ?? ''}
+ ethAddress={ethAddress}
+ emailDomain={emailDomain}
+ />
);
};
diff --git a/packages/frontend/src/components/projects/project/settings/RepoConnectedSection.tsx b/packages/frontend/src/components/projects/project/settings/RepoConnectedSection.tsx
index 594062f..f0a35e9 100644
--- a/packages/frontend/src/components/projects/project/settings/RepoConnectedSection.tsx
+++ b/packages/frontend/src/components/projects/project/settings/RepoConnectedSection.tsx
@@ -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
- setDisconnectRepoDialogOpen((preVal) => !preVal)}
+ setDisconnectRepoDialogOpen((preVal) => !preVal)}
open={disconnectRepoDialogOpen}
- confirmButtonTitle="Yes, confirm disconnect"
handleConfirm={() => {
setDisconnectRepoDialogOpen((preVal) => !preVal);
}}
- color="red"
- >
-
- Any data tied to your Git project may become misconfigured. Are you
- sure you want to continue?
-
-
+ />
);
};
diff --git a/packages/frontend/src/components/projects/project/settings/WebhookCard.tsx b/packages/frontend/src/components/projects/project/settings/WebhookCard.tsx
index 0d03e46..80aaf4f 100644
--- a/packages/frontend/src/components/projects/project/settings/WebhookCard.tsx
+++ b/packages/frontend/src/components/projects/project/settings/WebhookCard.tsx
@@ -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 (
{webhookUrl}
-
{
X
-
-
setDeleteDialogOpen((preVal) => !preVal)}
+ setDeleteDialogOpen((preVal) => !preVal)}
open={deleteDialogOpen}
- confirmButtonTitle="Yes, Confirm delete"
handleConfirm={() => {
setDeleteDialogOpen((preVal) => !preVal);
onDelete();
}}
- color="red"
- >
-
- Are you sure you want to delete the variable{' '}
- {webhookUrl} ?
-
-
+ webhookUrl={webhookUrl}
+ />
);
};
diff --git a/packages/frontend/src/components/shared/ConfirmDialog.tsx b/packages/frontend/src/components/shared/ConfirmDialog.tsx
index 1de20e9..e7374ad 100644
--- a/packages/frontend/src/components/shared/ConfirmDialog.tsx
+++ b/packages/frontend/src/components/shared/ConfirmDialog.tsx
@@ -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 (
-
-
-
- {dialogTitle}{' '}
-
-
- X
-
-
- {children}
-
-
- Cancel
-
-
- {confirmButtonTitle}
-
-
-
+
+
+ {dialogTitle}
+ {children}
+
+
+ Cancel
+
+
+ {confirmButtonTitle}
+
+
+
+
);
};
diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronDoubleDownIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronDoubleDownIcon.tsx
new file mode 100644
index 0000000..64993ac
--- /dev/null
+++ b/packages/frontend/src/components/shared/CustomIcon/ChevronDoubleDownIcon.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { CustomIcon, CustomIconProps } from './CustomIcon';
+
+export const ChevronDoubleDownIcon = (props: CustomIconProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts
index 29e3844..4d77a8b 100644
--- a/packages/frontend/src/components/shared/CustomIcon/index.ts
+++ b/packages/frontend/src/components/shared/CustomIcon/index.ts
@@ -61,6 +61,7 @@ export * from './CirclePlaceholderOnIcon';
export * from './WarningTriangleIcon';
export * from './CheckRadioOutlineIcon';
export * from './TrendingIcon';
+export * from './ChevronDoubleDownIcon';
// Templates
export * from './templates';
diff --git a/packages/frontend/src/components/shared/Modal/Modal.theme.ts b/packages/frontend/src/components/shared/Modal/Modal.theme.ts
new file mode 100644
index 0000000..4552ff7
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/Modal.theme.ts
@@ -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;
diff --git a/packages/frontend/src/components/shared/Modal/Modal.tsx b/packages/frontend/src/components/shared/Modal/Modal.tsx
new file mode 100644
index 0000000..ef9069f
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/Modal.tsx
@@ -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) => {
+ return (
+
+ {children}
+
+ );
+};
+
+Modal.Trigger = Trigger;
+Modal.Content = ModalContent;
+Modal.Header = ModalHeader;
+Modal.Footer = ModalFooter;
+Modal.Body = ModalBody;
diff --git a/packages/frontend/src/components/shared/Modal/ModalBody/ModalBody.tsx b/packages/frontend/src/components/shared/Modal/ModalBody/ModalBody.tsx
new file mode 100644
index 0000000..3079b26
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalBody/ModalBody.tsx
@@ -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) => {
+ const { body } = modalTheme();
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/Modal/ModalBody/index.ts b/packages/frontend/src/components/shared/Modal/ModalBody/index.ts
new file mode 100644
index 0000000..ad32ff0
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalBody/index.ts
@@ -0,0 +1 @@
+export * from './ModalBody';
diff --git a/packages/frontend/src/components/shared/Modal/ModalContent/ModalContent.tsx b/packages/frontend/src/components/shared/Modal/ModalContent/ModalContent.tsx
new file mode 100644
index 0000000..166256a
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalContent/ModalContent.tsx
@@ -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,
+ 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 (
+
+
+ }
+ >
+ {hasCloseButton && (
+
+
+
+
+
+ )}
+ {children}
+
+
+
+ );
+ },
+);
+
+ModalContent.displayName = 'ModalContent';
+
+export { ModalContent };
diff --git a/packages/frontend/src/components/shared/Modal/ModalContent/index.ts b/packages/frontend/src/components/shared/Modal/ModalContent/index.ts
new file mode 100644
index 0000000..79dee45
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalContent/index.ts
@@ -0,0 +1 @@
+export * from './ModalContent';
diff --git a/packages/frontend/src/components/shared/Modal/ModalFooter/ModalFooter.tsx b/packages/frontend/src/components/shared/Modal/ModalFooter/ModalFooter.tsx
new file mode 100644
index 0000000..5152e88
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalFooter/ModalFooter.tsx
@@ -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) => {
+ const { footer } = modalTheme({
+ className,
+ });
+
+ return (
+
+ );
+};
diff --git a/packages/frontend/src/components/shared/Modal/ModalFooter/index.ts b/packages/frontend/src/components/shared/Modal/ModalFooter/index.ts
new file mode 100644
index 0000000..2da6856
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalFooter/index.ts
@@ -0,0 +1 @@
+export * from './ModalFooter';
diff --git a/packages/frontend/src/components/shared/Modal/ModalHeader/ModalHeader.tsx b/packages/frontend/src/components/shared/Modal/ModalHeader/ModalHeader.tsx
new file mode 100644
index 0000000..c078384
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalHeader/ModalHeader.tsx
@@ -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) => {
+ const { header, headerDescription, headerTitle } = modalTheme();
+
+ return (
+ <>
+
+
+
+ {children}
+
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+ >
+ );
+};
diff --git a/packages/frontend/src/components/shared/Modal/ModalHeader/index.ts b/packages/frontend/src/components/shared/Modal/ModalHeader/index.ts
new file mode 100644
index 0000000..4424e62
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalHeader/index.ts
@@ -0,0 +1 @@
+export * from './ModalHeader';
diff --git a/packages/frontend/src/components/shared/Modal/ModalProvider.tsx b/packages/frontend/src/components/shared/Modal/ModalProvider.tsx
new file mode 100644
index 0000000..2be6beb
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/ModalProvider.tsx
@@ -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,
+ ModalProps {}
+
+type ModalProviderContext = ReturnType;
+
+const ModalContext = createContext | undefined>(
+ undefined,
+);
+
+// For inferring return type
+const useModalValues = (props: ModalProviderProps) => {
+ return props;
+};
+
+export const ModalProvider = ({
+ children,
+ ...props
+}: PropsWithChildren): JSX.Element => {
+ const values = useModalValues(props);
+
+ return (
+ {children}
+ );
+};
+
+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;
diff --git a/packages/frontend/src/components/shared/Modal/index.ts b/packages/frontend/src/components/shared/Modal/index.ts
new file mode 100644
index 0000000..cb89ee1
--- /dev/null
+++ b/packages/frontend/src/components/shared/Modal/index.ts
@@ -0,0 +1 @@
+export * from './Modal';
diff --git a/packages/frontend/src/components/shared/Tag/Tag.tsx b/packages/frontend/src/components/shared/Tag/Tag.tsx
index 8be34c8..08fb665 100644
--- a/packages/frontend/src/components/shared/Tag/Tag.tsx
+++ b/packages/frontend/src/components/shared/Tag/Tag.tsx
@@ -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.
diff --git a/packages/frontend/src/index.css b/packages/frontend/src/index.css
index 502904a..541fb61 100644
--- a/packages/frontend/src/index.css
+++ b/packages/frontend/src/index.css
@@ -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);
+ }
+ }
}
diff --git a/packages/frontend/src/layouts/ProjectSearch.tsx b/packages/frontend/src/layouts/ProjectSearch.tsx
index 5a71356..1343a5b 100644
--- a/packages/frontend/src/layouts/ProjectSearch.tsx
+++ b/packages/frontend/src/layouts/ProjectSearch.tsx
@@ -72,7 +72,7 @@ const ProjectSearch = () => {
{/* Content */}
-
diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx
index feee459..84e3e18 100644
--- a/packages/frontend/src/pages/components/index.tsx
+++ b/packages/frontend/src/pages/components/index.tsx
@@ -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();
@@ -57,6 +59,32 @@ const Page: React.FC = () => {
+ {/* Modal */}
+
+
+
Modal
+
+ {/* Modal example */}
+
+
+ Open modal
+
+
+ Modal title
+
+ Modal content
+
+
+ Close
+
+
+
+
+
+
+
+
+
{/* Steps */}
diff --git a/packages/frontend/src/pages/components/modals.tsx b/packages/frontend/src/pages/components/modals.tsx
new file mode 100644
index 0000000..63d34e2
--- /dev/null
+++ b/packages/frontend/src/pages/components/modals.tsx
@@ -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 (
+
+
+
Manual Storybook
+
+ Get started by editing{' '}
+
+ packages/frontend/src/pages/components/index.tsx
+
+
+
+
+
+ {/* Modal */}
+
+
+
Modal
+
+ {/* Modal example */}
+
+
+ Open modal
+
+
+ Modal title
+
+ Modal content
+
+
+ Close
+
+
+
+ {/* Transfer project */}
+
setOpenTransferDialog(true)}>
+ Transfer project
+
+
setOpenTransferDialog(!openTransferDialog)}
+ open={openTransferDialog}
+ handleConfirm={() => setOpenTransferDialog(!openTransferDialog)}
+ projectName="nextjs-boilerplate"
+ from="ayungavis"
+ to="Airfoil"
+ />
+ {/* Delete webhook */}
+ setDeleteDialogOpen(true)}>
+ Delete webhook
+
+ setDeleteDialogOpen((preVal) => !preVal)}
+ open={deleteDialogOpen}
+ handleConfirm={() => setDeleteDialogOpen((preVal) => !preVal)}
+ webhookUrl="examplehook.com"
+ />
+ {/* Disconnect repository */}
+ setDisconnectRepoDialogOpen(true)}>
+ Disconnect repository
+
+
+ setDisconnectRepoDialogOpen((preVal) => !preVal)
+ }
+ open={disconnectRepoDialogOpen}
+ handleConfirm={() => {
+ setDisconnectRepoDialogOpen((preVal) => !preVal);
+ }}
+ />
+ {/* Remove member */}
+ setRemoveMemberDialogOpen(true)}>
+ Remove member
+
+
+ setRemoveMemberDialogOpen((preVal) => !preVal)
+ }
+ open={removeMemberDialogOpen}
+ confirmButtonTitle="Yes, Remove member"
+ handleConfirm={() =>
+ setRemoveMemberDialogOpen((preVal) => !preVal)
+ }
+ memberName="John Doe"
+ ethAddress="0x1234567890"
+ emailDomain="example.com"
+ />
+ {/* Delete variable */}
+ setDeleteVariableDialogOpen(true)}>
+ Delete variable
+
+
+ setDeleteVariableDialogOpen((preVal) => !preVal)
+ }
+ open={deleteVariableDialogOpen}
+ handleConfirm={() =>
+ setDeleteVariableDialogOpen((preVal) => !preVal)
+ }
+ variableKey="AIUTH_TOKEN"
+ />
+ {/* Delete domain */}
+ setDeleteDomainDialogOpen(true)}>
+ Delete domain
+
+
+ setDeleteDomainDialogOpen((preVal) => !preVal)
+ }
+ open={deleteDomainDialogOpen}
+ handleConfirm={() =>
+ setDeleteDomainDialogOpen((preVal) => !preVal)
+ }
+ projectName="Airfoil"
+ domainName="airfoil.com"
+ />
+ {/* Cancel deployment */}
+ setCancelDeploymentDialogOpen(true)}>
+ Cancel deployment
+
+
+ setCancelDeploymentDialogOpen(!cancelDeploymentDialogOpen)
+ }
+ open={cancelDeploymentDialogOpen}
+ handleConfirm={() =>
+ setCancelDeploymentDialogOpen(!cancelDeploymentDialogOpen)
+ }
+ />
+ {/* Change to production */}
+ setChangeProductionDialogOpen(true)}>
+ Change to production
+
+ setChangeProductionDialogOpen(false)}
+ open={changeProductionDialogOpen}
+ handleConfirm={() => setChangeProductionDialogOpen(false)}
+ deployment={deployment}
+ domains={domains}
+ />
+ {/* Redeploy to production */}
+ setRedeployToProduction(true)}>
+ Redeploy to production
+
+
+ setRedeployToProduction((preVal) => !preVal)
+ }
+ open={redeployToProduction}
+ confirmButtonTitle="Redeploy"
+ handleConfirm={async () =>
+ setRedeployToProduction((preVal) => !preVal)
+ }
+ deployment={deployment}
+ domains={deployment.domain ? [deployment.domain] : []}
+ />
+ {/* Rollback to this deployment */}
+ setRollbackDeployment(true)}>
+ Rollback to this deployment
+
+ setRollbackDeployment((preVal) => !preVal)}
+ open={rollbackDeployment}
+ confirmButtonTitle="Rollback"
+ handleConfirm={async () =>
+ setRollbackDeployment((preVal) => !preVal)
+ }
+ deployment={deployment}
+ newDeployment={deployment}
+ domains={deployment.domain ? [deployment.domain] : []}
+ />
+
+
+
+
+
+ );
+};
+
+export default ModalsPage;
diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx
index 22d4208..8c1d0bd 100644
--- a/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx
+++ b/packages/frontend/src/pages/org-slug/projects/id/settings/General.tsx
@@ -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
-
setOpenTransferDialog(!openTransferDialog)}
+ setOpenTransferDialog(!openTransferDialog)}
open={openTransferDialog}
- confirmButtonTitle="Yes, Confirm transfer"
handleConfirm={handleTransferProject}
- color="blue"
- >
-
- Upon confirmation, your project {project.name} will be transferred
- from {project.organization.name} to {selectedUserOrgName}.
-
-
+ projectName={project.name}
+ from={project.organization.name}
+ to={selectedUserOrgName}
+ />