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
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
gap: 2em;
}

.swipeableCardsList > footer >div {
.swipeableCardsList > footer > div {
order: 2;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,71 +1,71 @@
.featureCell {
color: #0a0;
fill: currentColor;
place-self: start;
}
color: #0a0;
fill: currentColor;
place-self: start;
}

.featureCell.unavailable {
color: var(--color-neutral);
fill: currentColor;
}
.featureCell.unavailable {
color: var(--color-neutral);
fill: currentColor;
}

svg.featureCell {
width: 1.5em;
height: 1.5em;
}
svg.featureCell {
width: 1.5em;
height: 1.5em;
}

button.tooltip {
all: unset;
color: #0a0;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 0.5em;
position: relative;
width: fit-content;
}
button.tooltip {
all: unset;
color: #0a0;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 0.5em;
position: relative;
width: fit-content;
}

button.tooltip::after {
pointer-events: none;
content: attr(data-tooltip);
position: absolute;
right: 0;
top: 0;
width: max-content;
max-width: min(300px, 80vw);
overflow: hidden;
z-index: 999998;
background-color: var(--color-primary);
color: #fffe;
padding: 1.5em;
transform: translateY(calc(-100% - 12px)) translateX(8px);
border-radius: 4px;
transition: opacity 0.3s;
opacity: 0;
}
button.tooltip::after {
pointer-events: none;
content: attr(data-tooltip);
position: absolute;
right: 0;
top: 0;
width: max-content;
max-width: min(300px, 80vw);
overflow: hidden;
z-index: 999998;
background-color: var(--color-primary);
color: #fffe;
padding: 1.5em;
transform: translateY(calc(-100% - 12px)) translateX(8px);
border-radius: 4px;
transition: opacity 0.3s;
opacity: 0;
}

button.tooltip::before {
pointer-events: none;
content: '';
position: absolute;
right: 0;
top: 0;
width: 0;
height: 0;
border: 16px solid;
border-color: var(--color-primary) transparent transparent transparent;
z-index: 999999;
box-sizing: content-box;
transform: scaleX(0.5) translate(8px, -12px);
transition: opacity 0.3s;
opacity: 0;
}
button.tooltip::before {
pointer-events: none;
content: '';
position: absolute;
right: 0;
top: 0;
width: 0;
height: 0;
border: 16px solid;
border-color: var(--color-primary) transparent transparent transparent;
z-index: 999999;
box-sizing: content-box;
transform: scaleX(0.5) translate(8px, -12px);
transition: opacity 0.3s;
opacity: 0;
}

button.tooltip:hover::before,
button.tooltip:hover::after,
button.tooltip:active::before,
button.tooltip:active::after {
opacity: 1;
}
button.tooltip:hover::before,
button.tooltip:hover::after,
button.tooltip:active::before,
button.tooltip:active::after {
opacity: 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,11 @@ export default function VirtualLabSettingsComponent({ id }: { id: string }) {
const collapseItems: CollapseProps['items'] = useMemo(
() =>
// [header, costs, settings, plan, budget, billing, dangerZone].filter(
[costs, settings].filter(
[costs, settings, dangerZone].filter(
(item) => Object.keys(item).length !== 0 // Filter-out any "empty" panels (ex. DangerZone when not admin).
),
// [header, costs, settings, plan, budget, billing, dangerZone]
[costs, settings]
[costs, settings, dangerZone]
);

if (virtualLabDetail.state === 'loading') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { useEffect, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { CloseOutlined } from '@ant-design/icons';
import { Button, ConfigProvider, Input, InputRef, Modal, Spin } from 'antd';

function DeleteProjectConfirmation({
open,
onCancel,
onConfirm,
labName: projectName,
}: {
open: boolean;
labName: string;
onCancel: () => void;
onConfirm: () => void;
}) {
const inputRef = useRef<InputRef>(null);
const [incorrectValueText, setIncorrectValue] = useState<string | null>(null);

const confirmThenDelete = () => {
const expectedValue = `Delete ${projectName}`;
const actualValue = inputRef.current?.input?.value;

if (expectedValue === actualValue) {
setIncorrectValue(null);
onConfirm();
} else {
showValidationError(actualValue);
}
};

const showValidationError = (actualValue?: string) => {
if (!actualValue) {
setIncorrectValue('Please input Delete <your project name>');
} else if (!actualValue?.startsWith('Delete')) {
setIncorrectValue('The word "Delete" is missing in your input');
} else {
setIncorrectValue('The name of the project is incorrect');
}
};

return (
<ConfigProvider
theme={{
components: {
Modal: {
colorText: '#000',
paddingContentHorizontal: 36,
},
Input: {
colorBorder: 'transparent',
colorText: '#595959',
},
},
token: {
borderRadius: 0,
},
}}
>
<Modal
open={open}
className="text-center"
destroyOnClose
okButtonProps={{ className: 'hidden' }}
cancelButtonProps={{ className: 'hidden' }}
style={{ maxWidth: '348px' }}
styles={{ body: { maxWidth: '348px' } }}
closeIcon={<CloseOutlined onClick={onCancel} />}
>
<h3 className="text-xl font-bold">Are you sure you want to delete the project?</h3>
<p>
Type&nbsp;
<span className="bg-gray-100 px-2 text-gray-500">Delete &lt;your project name&gt;</span>
</p>

<div className="border-b-500 mt-5 border-b">
<Input
ref={inputRef}
placeholder="Write your confirmation here..."
aria-label="confirm project delete"
variant="borderless"
className="font-bold placeholder:font-normal"
/>
</div>
{incorrectValueText && <span className="text-error">{incorrectValueText}</span>}

<div className="mt-5">
<Button type="text" onClick={onCancel} className="text-gray-700">
Cancel
</Button>

<Button onClick={confirmThenDelete} className="bg-[#595959] text-white">
Confirm
</Button>
</div>
</Modal>
</ConfigProvider>
);
}

export default function DangerZonePanel({
onClick,
name,
}: {
name: string;
onClick: () => Promise<void>;
}) {
const { push } = useRouter();
const [deleted, setDeleted] = useState<boolean>(false);
const [infoText, setInfoText] = useState<string | null>(null);
const [showConfirmationDialog, setShowConfirmationDialog] = useState(false);
const [error, setError] = useState(false);
const [savingChanges, setSavingChanges] = useState(false);

let redirectTimeout: ReturnType<typeof setTimeout>;

const onDeletionConfirm = () => {
setShowConfirmationDialog(false);
setSavingChanges(true);

onClick()
.then(() => {
setError(false);

setInfoText(`You have now deleted ${name}.`);
})
.catch((response: { message: string }) => {
setError(true);

const { message } = response;

setInfoText(`There was an error when deleting this lab. ${message}.`);
})
.finally(() => {
setSavingChanges(false);
setDeleted(true);

redirectTimeout = setTimeout(() => push(`/app/virtual-lab`), 3000);
});
};

useEffect(() => clearTimeout(redirectTimeout));

return savingChanges ? (
<Spin />
) : (
<div className="flex items-center justify-between">
<DeleteProjectConfirmation
open={showConfirmationDialog}
onCancel={() => setShowConfirmationDialog(false)}
onConfirm={onDeletionConfirm}
labName={name}
/>

{infoText && <p className={error ? 'text-error' : 'text-white'}>{infoText}</p>}

<ConfigProvider
theme={{
components: {
Button: {
colorTextDisabled: '#fff',
},
},
}}
>
<Button
className="ml-auto h-14 rounded-none bg-neutral-3 font-semibold text-neutral-7"
danger
onClick={() => {
setShowConfirmationDialog(true);
}}
type="primary"
disabled={deleted}
>
Delete Project
</Button>
</ConfigProvider>
</div>
);
}
Loading