Add character deletion functionality with confirmation workflow
- Added `handleDeleteCharacter` method to handle character deletion with confirmation prompts. - Updated `CharacterComponent` and `CharacterDetail` to include delete button and related logic. - Localized new strings for character deletion (e.g., confirmation prompts, success/error messages). - Enhanced database repository methods (`deleteCharacter`) to handle character deletion securely. - Improved synchronization workflows to accommodate character deletion.
This commit is contained in:
@@ -28,6 +28,7 @@ interface CharacterDetailProps {
|
||||
attrId: string,
|
||||
) => void;
|
||||
handleSaveCharacter: () => void;
|
||||
handleDeleteCharacter: (characterId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const initialCharacterState: CharacterProps = {
|
||||
@@ -163,7 +164,41 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleDeleteCharacter(characterId: string): Promise<void> {
|
||||
try {
|
||||
let response: boolean;
|
||||
if (isCurrentlyOffline() || book?.localBook) {
|
||||
response = await window.electron.invoke<boolean>('db:character:delete', {
|
||||
characterId: characterId,
|
||||
});
|
||||
} else {
|
||||
response = await System.authDeleteToServer<boolean>('character/delete', {
|
||||
characterId: characterId,
|
||||
}, session.accessToken, lang);
|
||||
|
||||
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
||||
addToQueue('db:character:delete', {
|
||||
characterId: characterId,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!response) {
|
||||
errorMessage(t("characterComponent.errorDeleteCharacter"));
|
||||
return;
|
||||
}
|
||||
setCharacters(characters.filter((c: CharacterProps): boolean => c.id !== characterId));
|
||||
setSelectedCharacter(null);
|
||||
successMessage(t("characterComponent.successDelete"));
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t("common.unknownError"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addNewCharacter(updatedCharacter: CharacterProps): Promise<void> {
|
||||
if (!updatedCharacter.name) {
|
||||
errorMessage(t("characterComponent.errorNameRequired"));
|
||||
@@ -394,6 +429,7 @@ export function CharacterComponent({showToggle = true}: {showToggle?: boolean},
|
||||
handleRemoveElement={handleRemoveElement}
|
||||
handleCharacterChange={handleCharacterChange}
|
||||
handleSaveCharacter={handleSaveCharacter}
|
||||
handleDeleteCharacter={handleDeleteCharacter}
|
||||
/>
|
||||
) : (
|
||||
<CharacterList
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {Dispatch, SetStateAction, useContext, useEffect} from "react";
|
||||
import CharacterSectionElement from "@/components/book/settings/characters/CharacterSectionElement";
|
||||
import DeleteButton from "@/components/form/DeleteButton";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {LangContext} from "@/context/LangContext";
|
||||
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
||||
@@ -46,6 +47,7 @@ interface CharacterDetailProps {
|
||||
attrId: string,
|
||||
) => void;
|
||||
handleSaveCharacter: () => void;
|
||||
handleDeleteCharacter: (characterId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export default function CharacterDetail(
|
||||
@@ -56,6 +58,7 @@ export default function CharacterDetail(
|
||||
handleRemoveElement,
|
||||
handleAddElement,
|
||||
handleSaveCharacter,
|
||||
handleDeleteCharacter,
|
||||
}: CharacterDetailProps
|
||||
) {
|
||||
const t = useTranslations();
|
||||
@@ -64,7 +67,7 @@ export default function CharacterDetail(
|
||||
const {book} = useContext(BookContext);
|
||||
const {session} = useContext(SessionContext);
|
||||
const {errorMessage} = useContext(AlertContext);
|
||||
|
||||
|
||||
useEffect((): void => {
|
||||
if (selectedCharacter?.id !== null) {
|
||||
getAttributes().then();
|
||||
@@ -139,11 +142,22 @@ export default function CharacterDetail(
|
||||
<span className="text-text-primary font-semibold text-lg">
|
||||
{selectedCharacter?.name || t("characterDetail.newCharacter")}
|
||||
</span>
|
||||
<button onClick={handleSaveCharacter}
|
||||
className="flex items-center justify-center bg-primary w-10 h-10 rounded-xl border border-primary-dark shadow-md hover:shadow-lg hover:scale-110 transition-all duration-200">
|
||||
<FontAwesomeIcon icon={selectedCharacter?.id ? faSave : faPlus}
|
||||
className="text-text-primary w-5 h-5"/>
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedCharacter?.id && (
|
||||
<DeleteButton
|
||||
onDelete={(): Promise<void> => handleDeleteCharacter(selectedCharacter.id as string)}
|
||||
confirmTitle={t("characterDetail.deleteTitle")}
|
||||
confirmMessage={t("characterDetail.deleteMessage", {name: selectedCharacter.name})}
|
||||
confirmButtonText={t("common.delete")}
|
||||
cancelButtonText={t("common.cancel")}
|
||||
/>
|
||||
)}
|
||||
<button onClick={handleSaveCharacter}
|
||||
className="flex items-center justify-center bg-primary w-10 h-10 rounded-xl border border-primary-dark shadow-md hover:shadow-lg hover:scale-110 transition-all duration-200">
|
||||
<FontAwesomeIcon icon={selectedCharacter?.id ? faSave : faPlus}
|
||||
className="text-text-primary w-5 h-5"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(100vh-350px)] space-y-4 px-2 pb-4">
|
||||
|
||||
@@ -8,6 +8,7 @@ import TextInput from "@/components/form/TextInput";
|
||||
import TexteAreaInput from "@/components/form/TexteAreaInput";
|
||||
import SelectBox from "@/components/form/SelectBox";
|
||||
import SpellTagChip from "@/components/book/settings/spells/SpellTagChip";
|
||||
import DeleteButton from "@/components/form/DeleteButton";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowLeft,
|
||||
@@ -20,11 +21,9 @@ import {
|
||||
faSave,
|
||||
faStickyNote,
|
||||
faTags,
|
||||
faTrash,
|
||||
faTriangleExclamation
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useTranslations} from "next-intl";
|
||||
import AlertBox from "@/components/AlertBox";
|
||||
|
||||
interface SpellDetailProps {
|
||||
selectedSpell: SpellEditState;
|
||||
@@ -52,7 +51,6 @@ export default function SpellDetail(
|
||||
const [showTagDropdown, setShowTagDropdown] = useState<boolean>(false);
|
||||
const [isCreatingTag, setIsCreatingTag] = useState<boolean>(false);
|
||||
const [newTagColor, setNewTagColor] = useState<string>(defaultTagColors[0]);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState<boolean>(false);
|
||||
|
||||
function handleAddTag(tagId: string): void {
|
||||
if (!selectedSpell.tags.includes(tagId)) {
|
||||
@@ -118,12 +116,13 @@ export default function SpellDetail(
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedSpell.id && (
|
||||
<button
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
className="flex items-center justify-center bg-error/90 hover:bg-error w-10 h-10 rounded-xl border border-error shadow-md hover:shadow-lg hover:scale-110 transition-all duration-200"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} className="text-text-primary w-5 h-5"/>
|
||||
</button>
|
||||
<DeleteButton
|
||||
onDelete={(): Promise<void> => handleDeleteSpell(selectedSpell.id as string)}
|
||||
confirmTitle={t("spellDetail.deleteTitle")}
|
||||
confirmMessage={t("spellDetail.deleteMessage", {name: selectedSpell.name})}
|
||||
confirmButtonText={t("common.delete")}
|
||||
cancelButtonText={t("common.cancel")}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
onClick={handleSaveSpell}
|
||||
@@ -133,20 +132,6 @@ export default function SpellDetail(
|
||||
className="text-text-primary w-5 h-5"/>
|
||||
</button>
|
||||
</div>
|
||||
{showDeleteConfirm && selectedSpell.id && (
|
||||
<AlertBox
|
||||
title={t("spellDetail.deleteTitle")}
|
||||
message={t("spellDetail.deleteMessage", {name: selectedSpell.name})}
|
||||
type="danger"
|
||||
confirmText={t("common.delete")}
|
||||
cancelText={t("common.cancel")}
|
||||
onConfirm={async (): Promise<void> => {
|
||||
await handleDeleteSpell(selectedSpell.id as string);
|
||||
setShowDeleteConfirm(false);
|
||||
}}
|
||||
onCancel={(): void => setShowDeleteConfirm(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto max-h-[calc(100vh-350px)] space-y-4 px-2 pb-4">
|
||||
|
||||
Reference in New Issue
Block a user