refactor: Phase D — composant Modal réutilisable + utilitaire searchParams

- Crée Modal.tsx dans components/ui (backdrop, container, header sticky, close button)
- Remplace le scaffolding modal dupliqué dans EditBookForm, EditSeriesForm,
  DeleteBookButton, MetadataSearchModal (4 composants)
- Crée lib/searchParams.ts avec paramString, paramStringOr, paramInt, paramBool
- Simplifie le parsing des query params dans books, series, authors pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 12:23:50 +02:00
parent 13b1e1768e
commit 2670969d7e
10 changed files with 150 additions and 145 deletions

View File

@@ -1,9 +1,8 @@
"use client";
import { useState } from "react";
import { createPortal } from "react-dom";
import { useRouter } from "next/navigation";
import { Button, Icon } from "./ui";
import { Button, Icon, Modal } from "./ui";
import { useTranslation } from "@/lib/i18n/context";
export function DeleteBookButton({ bookId, libraryId }: { bookId: string; libraryId: string }) {
@@ -37,32 +36,24 @@ export function DeleteBookButton({ bookId, libraryId }: { bookId: string; librar
<span className="ml-1.5">{t("bookDetail.delete")}</span>
</Button>
{showConfirm && createPortal(
<>
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm z-50" onClick={() => setShowConfirm(false)} />
<div className="fixed inset-0 flex items-center justify-center z-50 p-4">
<div className="bg-card border border-border/50 rounded-xl shadow-2xl w-full max-w-sm overflow-hidden animate-in fade-in zoom-in-95 duration-200">
<div className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-2">
{t("bookDetail.delete")}
</h3>
<p className="text-sm text-muted-foreground">
{t("bookDetail.confirmDelete")}
</p>
</div>
<div className="flex justify-end gap-2 px-6 pb-6">
<Button variant="outline" size="sm" onClick={() => setShowConfirm(false)}>
{t("common.cancel")}
</Button>
<Button variant="destructive" size="sm" onClick={handleDelete}>
{t("bookDetail.delete")}
</Button>
</div>
</div>
</div>
</>,
document.body
)}
<Modal isOpen={showConfirm} onClose={() => setShowConfirm(false)} maxWidth="sm">
<div className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-2">
{t("bookDetail.delete")}
</h3>
<p className="text-sm text-muted-foreground">
{t("bookDetail.confirmDelete")}
</p>
</div>
<div className="flex justify-end gap-2 px-6 pb-6">
<Button variant="outline" size="sm" onClick={() => setShowConfirm(false)}>
{t("common.cancel")}
</Button>
<Button variant="destructive" size="sm" onClick={handleDelete}>
{t("bookDetail.delete")}
</Button>
</div>
</Modal>
</>
);
}