Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@
"DeleteConfirmationDialog": {
"deleteButton": "Delete",
"cancelButton": "Cancel",
"deleteMessage": "You are about to delete the resource",
"deleteMessageType": "Please type",
"deleteMessageConfirm": "to confirm deletion!"
"header": "Delete {{resourceName}}",
"deleteMessage": "You are about to delete the resource <b>{{resourceName}}</b>.",
"deleteConfirmation": "To confirm, type “{{resourceName}}” in the box below"
},
"Loading": {
"title": "Getting Ready",
Expand Down Expand Up @@ -157,7 +157,9 @@
},
"ProjectsListView": {
"pageTitle": "Let's get started",
"title": "Projects"
"title": "Projects",
"deleteProject": "Delete project",
"deleteConfirmationDialog": "Project deleted"
},
"ControlPlaneView": {
"accessError": "Managed Control Plane does not have access information yet",
Expand Down Expand Up @@ -190,6 +192,12 @@
"mainCommandDescription": "Run this command to delete the workspace:",
"verificationCommandDescription": "To verify the workspace has been deleted, run:"
},
"DeleteProjectDialog": {
"title": "Delete a Project",
"introSection1": "The below instructions will help you delete the project named \"{{projectName}}\" using kubectl.",
"introSection2": "Remember that this action is <bold1>irreversible</bold1> and all resources within the project will be <bold2>permanently deleted</bold2>.",
"mainCommandDescription": "Run this command to delete the project:"
},
"KubectlDeleteMcpDialog": {
"title": "Delete a Managed Control Plane",
"introSection1": "The below will help you delete the Managed Control Plane \"{{mcpName}}\" from workspace \"{{workspaceNamespace}}\" using kubectl.",
Expand Down
34 changes: 9 additions & 25 deletions src/components/Dialogs/DeleteConfirmationDialog.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ describe('DeleteConfirmationDialog', () => {

cy.get('ui5-dialog').should('be.visible').should('have.attr', 'open');

cy.contains('Confirm deletion').should('be.visible');
cy.contains('Delete test-resource').should('be.visible');

cy.contains('You are about to delete the resource test-resource').should(
'be.visible',
);
cy.contains('You are about to delete the resource test-resource').should('be.visible');
});

it('should not be visible when isOpen is false', () => {
Expand All @@ -44,19 +42,15 @@ describe('DeleteConfirmationDialog', () => {
it('should enable Delete button when correct resource name is typed', () => {
mountDialog();

cy.get('ui5-input[id*="mcp-name-input"]')
.find(' input[id*="inner"]')
.type('test-resource', { force: true });
cy.get('ui5-input[id*="mcp-name-input"]').find(' input[id*="inner"]').type('test-resource', { force: true });

cy.get('ui5-button').contains('Delete').should('not.have.attr', 'disabled');
});

it('should keep Delete button disabled when incorrect name is typed', () => {
mountDialog();

cy.get('ui5-input[id*="mcp-name-input"]')
.find(' input[id*="inner"]')
.type('wrong-name', { force: true });
cy.get('ui5-input[id*="mcp-name-input"]').find(' input[id*="inner"]').type('wrong-name', { force: true });

cy.get('ui5-button').contains('Delete').should('have.attr', 'disabled');
});
Expand All @@ -74,9 +68,7 @@ describe('DeleteConfirmationDialog', () => {
it('should call onDeletionConfirmed and setIsOpen when Delete is confirmed', () => {
mountDialog();

cy.get('ui5-input[id*="mcp-name-input"]')
.find(' input[id*="inner"]')
.type('test-resource');
cy.get('ui5-input[id*="mcp-name-input"]').find(' input[id*="inner"]').type('test-resource');

cy.get('ui5-button').contains('Delete').click();

Expand All @@ -90,30 +82,22 @@ describe('DeleteConfirmationDialog', () => {
mountDialog();

// Type something
cy.get('ui5-input[id*="mcp-name-input"]')
.find(' input[id*="inner"]')
.type('test-resource', { force: true });
cy.get('ui5-input[id*="mcp-name-input"]').find(' input[id*="inner"]').type('test-resource', { force: true });

// Close dialog
cy.get('ui5-button').contains('Cancel').click();

// Reopen dialog
mountDialog();

cy.get('ui5-input[id*="mcp-name-input"]')
.find(' input[id*="inner"]')
.should('have.value', '');
cy.get('ui5-input[id*="mcp-name-input"]').find(' input[id*="inner"]').should('have.value', '');
});

it('should display correct resource name in all labels', () => {
mountDialog({ resourceName: 'custom-resource' });

cy.contains('You are about to delete the resource custom-resource.').should(
'be.visible',
);
cy.contains('You are about to delete the resource custom-resource.').should('be.visible');

cy.contains('Please type custom-resource to confirm deletion!').should(
'be.visible',
);
cy.contains('To confirm, type “custom-resource” in the box below').should('be.visible');
});
});
20 changes: 20 additions & 0 deletions src/components/Dialogs/DeleteConfirmationDialog.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.dialogContent {
display: flex;
flex-direction: column;
margin-block-end: 1rem;
align-items: flex-start;
}

.message {
color: var(--sapContent_LabelColor);
}

.confirmLabel {
margin-block-start: 1rem;
font-weight: bold;
}

.confirmationInput {
width: 100%;
}

163 changes: 65 additions & 98 deletions src/components/Dialogs/DeleteConfirmationDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { ReactNode, useEffect, useRef, useState } from 'react';
import {
Bar,
Button,
Dialog,
Form,
FormGroup,
FormItem,
Input,
InputDomRef,
Label,
} from '@ui5/webcomponents-react';
import { ReactNode, useState } from 'react';
import { Bar, Button, Dialog, Input, InputDomRef, Label } from '@ui5/webcomponents-react';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import { useTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';

import styles from './DeleteConfirmationDialog.module.css';
import type { Ui5CustomEvent } from '@ui5/webcomponents-react-base';

interface DeleteConfirmationDialogProps {
isOpen: boolean;
Expand All @@ -30,96 +23,70 @@ export function DeleteConfirmationDialog({
onCanceled,
kubectl,
}: DeleteConfirmationDialogProps) {
const [confirmed, setConfirmed] = useState(false);
const confirmationInput = useRef<InputDomRef>(null);
const [confirmationText, setConfirmationText] = useState('');
const { t } = useTranslation();

useEffect(() => {
return () => {
setConfirmed(false);
if (confirmationInput.current) {
confirmationInput.current.value = '';
}
};
}, [isOpen]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onConfirmationInputChange = (event: any) => {
if (event.target.value === resourceName) {
setConfirmed(true);
} else {
setConfirmed(false);
}
const onConfirmationInputChange = (event: Ui5CustomEvent<InputDomRef>) => {
setConfirmationText(event.target.value);
};

const isConfirmed = confirmationText === resourceName;

return (
<>
<Dialog
stretch={false}
headerText="Confirm deletion"
open={isOpen}
footer={
<Bar
design="Footer"
endContent={
<>
<Button
design={ButtonDesign.Transparent}
onClick={() => {
setIsOpen(false);
onCanceled && onCanceled();
}}
>
{t('DeleteConfirmationDialog.cancelButton')}
</Button>
<Button
design={ButtonDesign.Negative}
disabled={confirmed === false}
onClick={() => {
setIsOpen(false);
onDeletionConfirmed && onDeletionConfirmed();
}}
>
{t('DeleteConfirmationDialog.deleteButton')}
</Button>
</>
}
<Dialog
stretch={false}
headerText={t('DeleteConfirmationDialog.header', { resourceName })}
open={isOpen}
footer={
<Bar
design="Footer"
endContent={
<>
<Button
design={ButtonDesign.Transparent}
onClick={() => {
setIsOpen(false);
onCanceled && onCanceled();
}}
>
{t('DeleteConfirmationDialog.cancelButton')}
</Button>
<Button
design={ButtonDesign.Negative}
disabled={!isConfirmed}
onClick={() => {
setIsOpen(false);
onDeletionConfirmed && onDeletionConfirmed();
}}
>
{t('DeleteConfirmationDialog.deleteButton')}
</Button>
{kubectl}
</>
}
/>
}
>
<div className={styles.dialogContent}>
<span className={styles.message}>
<Trans
i18nKey="DeleteConfirmationDialog.deleteMessage"
values={{ resourceName }}
components={{
b: <b />,
}}
/>
}
>
<Form layout="S1 M1 L1 XL1">
<FormGroup>
<Label>
{t('DeleteConfirmationDialog.deleteMessage')} {resourceName}.
</Label>
<Label>
{' '}
{t('DeleteConfirmationDialog.deleteMessageType')}{' '}
<b>{resourceName}</b>{' '}
{t('DeleteConfirmationDialog.deleteMessageConfirm')}
</Label>
</FormGroup>
<FormGroup>
<FormItem
labelContent={
<Label>
{t('DeleteConfirmationDialog.deleteMessageType')}{' '}
{resourceName}{' '}
{t('DeleteConfirmationDialog.deleteMessageConfirm')}
</Label>
}
>
<Input
ref={confirmationInput}
id="mcp-name-input"
placeholder=""
onInput={onConfirmationInputChange}
/>
</FormItem>
<FormItem>{kubectl}</FormItem>
</FormGroup>
</Form>
</Dialog>
</>
</span>
<Label className={styles.confirmLabel} for="mcp-name-input">
{t('DeleteConfirmationDialog.deleteConfirmation', { resourceName })}
</Label>
<Input
id="mcp-name-input"
value={confirmationText}
className={styles.confirmationInput}
onInput={onConfirmationInputChange}
/>
</div>
</Dialog>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState } from 'react';

import { KubectlInfoButton } from '../KubectlInfoButton';
import { KubectlDeleteProjectDialog } from '../KubectlDeleteProjectDialog.tsx';

interface KubectlDeleteProjectProps {
projectName?: string;
}

export const KubectlDeleteProject = ({ projectName }: KubectlDeleteProjectProps) => {
const [isInfoDialogOpen, setIsInfoDialogOpen] = useState(false);

const openInfoDialog = () => setIsInfoDialogOpen(true);
const closeInfoDialog = () => setIsInfoDialogOpen(false);

return (
<>
<KubectlInfoButton onClick={openInfoDialog} />
<KubectlDeleteProjectDialog projectName={projectName} isOpen={isInfoDialogOpen} onClose={closeInfoDialog} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { KubectlBaseDialog, CustomCommand } from './KubectlBaseDialog';
import { Text } from '@ui5/webcomponents-react';
import { useTranslation, Trans } from 'react-i18next';
import { Fragment } from 'react/jsx-runtime';

interface KubectlDeleteProjectDialogProps {
onClose: () => void;
resourceName?: string;
projectName?: string;
isOpen: boolean;
}

export const KubectlDeleteProjectDialog = ({ onClose, projectName, isOpen }: KubectlDeleteProjectDialogProps) => {
const { t } = useTranslation();

const projectNamespace = projectName ?? '<project-names>"';

const customCommands: CustomCommand[] = [
{
command: `kubectl delete project ${projectNamespace}`,
description: t('DeleteProjectDialog.mainCommandDescription'),
isMainCommand: true,
},
];

const introSection = [
<Fragment key="intro-1">
<Text>
{t('DeleteProjectDialog.introSection1', {
projectName,
})}
</Text>
<Text>
<Trans
i18nKey="DeleteProjectDialog.introSection2"
components={{
bold1: <b />,
bold2: <b />,
}}
/>
</Text>
</Fragment>,
];

return (
<KubectlBaseDialog
title={t('DeleteProjectDialog.title')}
introSection={introSection}
customCommands={customCommands}
open={isOpen}
onClose={onClose}
/>
);
};
Loading
Loading