mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-10-26 08:06:09 +01:00
Implement confirmation dialog before repository delete
Squash commits of branch feature/confirm_repo_delete: - Implement confirmation dialog before repository delete - Change repo alert - The input does not support autocomplete Therefore it should not be announced as an autocomplete input by assistive technologies. - Change repo alert - Remove unused import - Update snapshots
This commit is contained in:
@@ -62,8 +62,8 @@ Das gewählte Repository wird zum SCM-Manager hinzugefügt und sämtliche Reposi
|
||||
|
||||

|
||||
|
||||
### Repository Informationen
|
||||
Die Informationsseite eines Repository zeigt die Metadaten zum Repository an. Darunter befinden sich Beschreibungen zu den unterschiedlichen Möglichkeiten wie man mit diesem Repository arbeiten kann.
|
||||
### Repositoryinformationen
|
||||
Die Informationsseite eines Repositorys zeigt die Metadaten zum Repository an. Darunter befinden sich Beschreibungen zu den unterschiedlichen Möglichkeiten wie man mit diesem Repository arbeiten kann.
|
||||
In der Überschrift kann der Namespace angeklickt werden, um alle Repositories aus diesem Namespace anzuzeigen.
|
||||
|
||||

|
||||
|
||||
@@ -45,10 +45,10 @@ eingebunden sind. Wenn bei dem Zugriff auf ein Repository Fehler auftreten, soll
|
||||
Integritätsprüfung gestartet werden. Ein Teil dieser Prüfungen wird bei jedem Start des SCM-Managers ausgeführt.
|
||||
|
||||
Werden bei einer dieser Integritätsprüfungen Fehler gefunden, wird auf der Repository-Übersicht sowie auf den
|
||||
Detailseiten zum Repository neben dem Namen ein Tag „fehlerhaft" angezeigt. In den Einstellungen wird zudem eine Meldung
|
||||
Detailseiten zum Repository neben dem Namen ein Tag „fehlerhaft“ angezeigt. In den Einstellungen wird zudem eine Meldung
|
||||
eingeblendet. Durch Klick auf diese Meldung oder die Tags wird ein Popup mit weiteren Details angezeigt.
|
||||
|
||||
Der Server führt immer nur eine Prüfung zur Zeit durch. Es können jedoch für mehrere Repositories Prüfungen in die
|
||||
Der Server führt immer nur eine Prüfung zurzeit durch. Es können jedoch für mehrere Repositories Prüfungen in die
|
||||
Warteschlange gestellt werden, die dann nacheinander durchgeführt werden.
|
||||
|
||||

|
||||
|
||||
2
gradle/changelog/delete_confirm_dialog.yaml
Normal file
2
gradle/changelog/delete_confirm_dialog.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: added
|
||||
description: Confirmation dialog before repository deletion
|
||||
@@ -17047,6 +17047,7 @@ Array [
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob.
|
||||
|
||||
</section>
|
||||
<footer
|
||||
className="modal-card-foot"
|
||||
@@ -17195,6 +17196,7 @@ Array [
|
||||
hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance with
|
||||
Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly
|
||||
ordinary mob.
|
||||
|
||||
</section>
|
||||
<footer
|
||||
className="modal-card-foot"
|
||||
|
||||
@@ -26,16 +26,17 @@ type Button = {
|
||||
isLoading?: boolean;
|
||||
onClick?: () => void | null;
|
||||
autofocus?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
message: string;
|
||||
message?: string;
|
||||
buttons: Button[];
|
||||
close?: () => void;
|
||||
};
|
||||
|
||||
export const ConfirmAlert: FC<Props> = ({ title, message, buttons, close }) => {
|
||||
export const ConfirmAlert: FC<Props> = ({ title, message, buttons, close, children }) => {
|
||||
const [showModal, setShowModal] = useState(true);
|
||||
const initialFocusButton = useRef<HTMLButtonElement>(null);
|
||||
|
||||
@@ -54,7 +55,11 @@ export const ConfirmAlert: FC<Props> = ({ title, message, buttons, close }) => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const body = <>{message}</>;
|
||||
const body = (
|
||||
<>
|
||||
{message} {children}
|
||||
</>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="field is-grouped">
|
||||
@@ -63,8 +68,9 @@ export const ConfirmAlert: FC<Props> = ({ title, message, buttons, close }) => {
|
||||
<button
|
||||
className={classNames("button", button.className, button.isLoading ? "is-loading" : "")}
|
||||
key={index}
|
||||
disabled={button.disabled}
|
||||
onClick={() => handleClickButton(button)}
|
||||
onKeyDown={e => e.key === "Enter" && handleClickButton(button)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleClickButton(button)}
|
||||
tabIndex={0}
|
||||
ref={button.autofocus ? initialFocusButton : undefined}
|
||||
>
|
||||
|
||||
@@ -18,9 +18,11 @@ import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import { createVariantClass, Variant } from "../../variants";
|
||||
|
||||
type Props = { variant?: Variant; className?: string; children?: ReactNode };
|
||||
type Props = { id?: string; variant?: Variant; className?: string; children?: ReactNode };
|
||||
|
||||
const FieldMessage = ({ variant, className, children }: Props) => (
|
||||
<p className={classNames("help", createVariantClass(variant), className)}>{children}</p>
|
||||
const FieldMessage = ({ id, variant, className, children }: Props) => (
|
||||
<p id={id} className={classNames("help", createVariantClass(variant), className)}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
export default FieldMessage;
|
||||
|
||||
@@ -46,6 +46,7 @@ const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
|
||||
const inputId = useAriaId(id ?? props.testId);
|
||||
const helpTextId = helpText ? `input-helptext-${name}` : undefined;
|
||||
const descriptionId = descriptionText ? `input-description-${name}` : undefined;
|
||||
const errorId = error ? `input-error-${name}` : undefined;
|
||||
const variant = error ? "danger" : undefined;
|
||||
return (
|
||||
<Field className={className}>
|
||||
@@ -74,7 +75,11 @@ const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
|
||||
</span>
|
||||
) : null}
|
||||
</Control>
|
||||
{error ? <FieldMessage variant={variant}>{error}</FieldMessage> : null}
|
||||
{error ? (
|
||||
<FieldMessage id={errorId} variant={variant}>
|
||||
{error}
|
||||
</FieldMessage>
|
||||
) : null}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
"confirmAlert": {
|
||||
"title": "Branch löschen",
|
||||
"message": "Möchten Sie den Branch \"{{branch}}\" wirklich löschen?",
|
||||
"typeNameConfirmation": "Bitte geben Sie \"{{confirmationText}}\" ein, um Ihre Entscheidung zu bestätigen.",
|
||||
"inputNotMatching": "Die Eingabe stimmt nicht überein.",
|
||||
"cancel": "Nein",
|
||||
"submit": "Ja"
|
||||
}
|
||||
@@ -515,6 +517,8 @@
|
||||
"confirmAlert": {
|
||||
"title": "Repository löschen",
|
||||
"message": "Soll das Repository wirklich gelöscht werden?",
|
||||
"confirmationText": "Geben Sie \"{{confirmationText}}\" ein, um das Löschen des Repositories zu bestätigen.",
|
||||
"inputNotMatching": "Die Eingabe stimmt nicht überein.",
|
||||
"submit": "Ja",
|
||||
"cancel": "Nein"
|
||||
}
|
||||
|
||||
@@ -515,6 +515,8 @@
|
||||
"confirmAlert": {
|
||||
"title": "Delete Repository",
|
||||
"message": "Do you really want to delete the repository?",
|
||||
"confirmationText": "Enter \"{{confirmationText}}\" to confirm the deletion of the repository.",
|
||||
"inputNotMatching": "The input doesn't match.",
|
||||
"submit": "Yes",
|
||||
"cancel": "No"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { ConfirmAlert, DeleteButton, ErrorNotification, Level } from "@scm-manager/ui-components";
|
||||
import { useDeleteRepository } from "@scm-manager/ui-api";
|
||||
import { InputField } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
@@ -31,9 +32,12 @@ const DeleteRepo: FC<Props> = ({ repository, confirmDialog = true }) => {
|
||||
const { isLoading, error, remove, isDeleted } = useDeleteRepository({
|
||||
onSuccess: () => {
|
||||
history.push("/repos/");
|
||||
}
|
||||
},
|
||||
});
|
||||
const [showConfirmAlert, setShowConfirmAlert] = useState(false);
|
||||
const [enteredValue, setEnteredValue] = useState("");
|
||||
const [inputError, setInputError] = useState();
|
||||
const [errorId, setErrorId] = useState(error ? "input-error-delete" : undefined);
|
||||
const [t] = useTranslation("repos");
|
||||
useEffect(() => {
|
||||
if (isDeleted) {
|
||||
@@ -47,29 +51,60 @@ const DeleteRepo: FC<Props> = ({ repository, confirmDialog = true }) => {
|
||||
|
||||
const confirmDelete = () => {
|
||||
setShowConfirmAlert(true);
|
||||
setEnteredValue("");
|
||||
setInputError(undefined);
|
||||
setErrorId(undefined);
|
||||
};
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEnteredValue(e.target.value);
|
||||
if (isInvalidEnteredValue && !inputError) {
|
||||
setInputError(t("deleteRepo.confirmAlert.inputNotMatching"));
|
||||
setErrorId("input-error-delete");
|
||||
}
|
||||
};
|
||||
|
||||
const action = confirmDialog ? confirmDelete : deleteRepoCallback;
|
||||
|
||||
const confirmationText = `${repository.namespace}/${repository.name}`;
|
||||
const isInvalidEnteredValue = enteredValue !== confirmationText;
|
||||
|
||||
const confirmationDialog = (
|
||||
<>
|
||||
<InputField
|
||||
name="delete"
|
||||
label={t("deleteRepo.confirmAlert.confirmationText", { confirmationText: confirmationText })}
|
||||
value={enteredValue}
|
||||
onChange={handleChange}
|
||||
error={isInvalidEnteredValue ? inputError : undefined}
|
||||
aria-describedby={errorId}
|
||||
aria-invalid={errorId !== undefined}
|
||||
autoComplete="off"
|
||||
></InputField>
|
||||
</>
|
||||
);
|
||||
|
||||
if (showConfirmAlert) {
|
||||
return (
|
||||
<ConfirmAlert
|
||||
title={t("deleteRepo.confirmAlert.title")}
|
||||
message={t("deleteRepo.confirmAlert.message")}
|
||||
buttons={[
|
||||
{
|
||||
label: t("deleteRepo.confirmAlert.submit"),
|
||||
onClick: () => deleteRepoCallback()
|
||||
onClick: () => deleteRepoCallback(),
|
||||
disabled: isInvalidEnteredValue,
|
||||
},
|
||||
{
|
||||
className: "is-info",
|
||||
label: t("deleteRepo.confirmAlert.cancel"),
|
||||
onClick: () => null,
|
||||
autofocus: true
|
||||
}
|
||||
autofocus: true,
|
||||
},
|
||||
]}
|
||||
close={() => setShowConfirmAlert(false)}
|
||||
/>
|
||||
>
|
||||
{confirmationDialog}
|
||||
</ConfirmAlert>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user