Files
IdeA/frontend/src/features/projects/RemoveProjectDialog.tsx
Blomios 1a1429f10e feat(domain,application,infrastructure,backend,web-server,app-tauri,frontend): lot utilisateur #325 #326 — icônes projet avec watermark CLI custom et localisation FR/EN (QA verte)
Lot en un seul commit: les deux features partagent les mêmes contrats
(domain/settings.rs, DTO, commandes Tauri/HTTP, web-server) et les mêmes
surfaces UI — la séparation par fichiers était impossible sans chirurgie
de hunks risquant des états intermédiaires non compilables (convention
lot des batches #310-#313 et #315).

#325 — icône configurable par projet, watermark CLI custom:
- domaine: ProjectIconSet (devicon, simple-icons, lucide, idea),
  ProjectIconRef validé (nom ASCII 1..=64, -_. internes), champ icon
  optionnel sur ProjectSettings; sérialisation camelCase et try_from
  strict
- application: UpdateProjectSettings.execute accepte un patch
  theme_color OU icon (absent = inchangé, null = défaut produit), tests
  in-dependants avec store mémoire
- backend/app-tauri/web-server: ProjectIconRefDto, UpdateProjectSettingsRequestDto
  avec icon optionnel (désérialisation additive), commandes
  update_project_settings alignées Tauri/HTTP, tests DTO et HTTP
  (défaut null, set, clear, nom invalide rejeté)
- frontend: catalogue projectIcons (recherche EN), ProjectIconPicker en
  grille multi-lignes dans Paramètres projet, affichage de l'icône dans
  ProjectTabs et watermark grand format à faible opacité en fond de CLI
  custom; ports ProjectSettings.icon, Partial<ProjectSettings>,
  adapters tauri/mock/http et tests alignés

#326 — localisation FR/EN et traduction des textes IdeA:
- domaine: IdeaLanguage (fr | en), champ language optionnel sur
  IdeaUserSettings (absence = défaut produit, série additive validée)
- backend/web-server: UserSettingsDto.language, update_user_settings
  accepte language, tests HTTP alignés; fixtures structured_launch_d3
  mises à jour
- frontend: i18n/ (catalogue FR/EN, I18nProvider, scan anti-régression
  des sources visibles), GeneralSettings avec sélecteur de langue dans
  les paramètres, setupI18n dans vitest; migration de l'ensemble des
  textes hardcodés vers le catalogue sur toutes les surfaces (agents,
  layout, projets, tickets, sprints, web, plugins, permissions, git,
  templates, skills, mémoire, model servers, embedder, first-run,
  devices, scheduled tasks, workstate, dialogs), FR conservé par défaut;
  recherche d'icônes #325 en anglais conformément au ticket

QA verte sur feature/batch-325-326-project-icons-localisation. Aucun push.
2026-09-11 21:53:56 +02:00

96 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useRef } from "react";
import type { Project } from "@/domain";
import { Button, zIndex } from "@/shared";
import { useI18n } from "@/i18n";
interface RemoveProjectDialogProps {
project: Project;
isOpen: boolean;
pending: boolean;
error: string | null;
onCancel: () => void;
onConfirm: () => void;
}
export function RemoveProjectDialog({
project,
isOpen,
pending,
error,
onCancel,
onConfirm,
}: RemoveProjectDialogProps) {
const { tr } = useI18n();
const cancelRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
cancelRef.current?.focus();
}, []);
useEffect(() => {
function cancelOnEscape(event: KeyboardEvent) {
if (event.key === "Escape" && !pending) onCancel();
}
document.addEventListener("keydown", cancelOnEscape);
return () => document.removeEventListener("keydown", cancelOnEscape);
}, [onCancel, pending]);
return (
<div
className="fixed inset-0 flex items-center justify-center bg-black/55 p-4"
style={{ zIndex: zIndex.floatingWindowNested }}
onMouseDown={(event) => {
if (!pending && event.target === event.currentTarget) onCancel();
}}
>
<div
role="alertdialog"
aria-modal="true"
aria-label={tr(`Retirer « ${project.name} » d’IdeA ?`, `Remove “${project.name}” from IdeA?`)}
className="flex w-full max-w-md flex-col gap-3 rounded-lg border border-border bg-surface p-4 shadow-xl"
>
<h3 className="text-sm font-semibold text-content">
{tr(`Retirer « ${project.name} » d’IdeA ?`, `Remove “${project.name}” from IdeA?`)}
</h3>
<code className="break-all rounded-md bg-raised px-3 py-2 text-xs text-muted">
{project.root}
</code>
<p className="text-sm text-muted">
{tr("Le dossier et les fichiers resteront sur le disque.", "The folder and files will remain on disk.")}
</p>
{isOpen && (
<p className="text-sm text-muted">
{tr("Les vues de ce projet seront fermées et les opérations IdeA en cours seront interrompues.", "This project's views will close and its active IdeA operations will be interrupted.")}
</p>
)}
{error && (
<p role="alert" className="rounded-md border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger">
{error}
</p>
)}
<div className="flex justify-end gap-2">
<Button
ref={cancelRef}
size="sm"
variant="ghost"
disabled={pending}
onClick={onCancel}
>
{tr("Annuler", "Cancel")}
</Button>
<Button
size="sm"
variant="danger"
loading={pending}
aria-label={pending ? tr("Retrait…", "Removing…") : undefined}
onClick={onConfirm}
>
{pending ? tr("Retrait…", "Removing…") : tr("Retirer d’IdeA", "Remove from IdeA")}
</Button>
</div>
</div>
</div>
);
}