mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-17 20:30:07 +02:00
fix(ui): message clair quand l'onglet est périmé après un déploiement
« Server Action "…" was not found on the server » n'est pas une panne du snapshot : Next.js identifie chaque Server Action par un hash calculé à la compilation, et un onglet ouvert avant un nouveau build continue d'envoyer les anciens identifiants. La seule issue est de recharger la page. - src/lib/stale-action.ts : détection de cette erreur + message utilisateur. - Grille et Snapshots : au lieu d'afficher l'erreur technique brute, on propose « Recharger la page ». Les brouillons de la Grille sont persistés (localStorage), recharger ne perd rien. - SuccessModal accepte `variant="error"` : icône d'alerte rouge au lieu de la coche verte, et surtout plus de fermeture automatique au bout de 3 s — un message d'échec doit rester lisible. Les erreurs s'affichaient jusqu'ici sous une coche de succès. - SuccessModal accepte une action secondaire optionnelle, et joue son animation d'entrée en CSS au lieu d'un setState synchrone dans un effet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
This commit is contained in:
4 files changed
+149
-33
No files matched your search
@@ -7,6 +7,7 @@ import { Loader2, CheckCircle, AlertCircle, RotateCcw, Camera, ChevronDown } fro
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
import { saveSnapshot } from "@/features/snapshots/api/save-snapshot";
|
||||
import { SuccessModal } from "@/components/shared/success-modal";
|
||||
import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -37,7 +38,13 @@ export function FloatingSummaryBar() {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [isSavingSnapshot, setIsSavingSnapshot] = useState(false);
|
||||
const [saveStatus, setSaveStatus] = useState<"idle" | "success" | "error">("idle");
|
||||
const [modal, setModal] = useState<{ isOpen: boolean, title: string, message: string }>({
|
||||
const [modal, setModal] = useState<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
variant?: "success" | "error";
|
||||
action?: { label: string; onClick: () => void };
|
||||
}>({
|
||||
isOpen: false,
|
||||
title: "",
|
||||
message: ""
|
||||
@@ -118,12 +125,26 @@ export function FloatingSummaryBar() {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (!labelOverride) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setModal({
|
||||
isOpen: true,
|
||||
title: "Erreur Snapshot",
|
||||
message: `Impossible de créer le snapshot : ${msg}`
|
||||
});
|
||||
// Onglet resté ouvert pendant un déploiement : les identifiants de
|
||||
// Server Action ont changé côté serveur. Rien à réparer côté
|
||||
// métier, il faut recharger le bundle client.
|
||||
if (isStaleServerActionError(err)) {
|
||||
setModal({
|
||||
isOpen: true,
|
||||
title: "Page à recharger",
|
||||
message: STALE_ACTION_MESSAGE,
|
||||
variant: "error",
|
||||
action: { label: "Recharger la page", onClick: () => window.location.reload() },
|
||||
});
|
||||
} else {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
setModal({
|
||||
isOpen: true,
|
||||
title: "Erreur Snapshot",
|
||||
message: `Impossible de créer le snapshot : ${msg}`,
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setIsSavingSnapshot(false);
|
||||
@@ -327,6 +348,8 @@ export function FloatingSummaryBar() {
|
||||
onClose={() => setModal(prev => ({ ...prev, isOpen: false }))}
|
||||
title={modal.title}
|
||||
message={modal.message}
|
||||
variant={modal.variant}
|
||||
action={modal.action}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
import { SuccessModal } from "@/components/shared/success-modal";
|
||||
import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action";
|
||||
import { ConfirmModal } from "@/components/shared/confirm-modal";
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
@@ -36,12 +37,34 @@ export function SnapshotList({ type }: SnapshotListProps) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState<number | null>(null);
|
||||
const [modal, setModal] = useState<{ isOpen: boolean, title: string, message: string }>({
|
||||
const [modal, setModal] = useState<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
variant?: "success" | "error";
|
||||
action?: { label: string; onClick: () => void };
|
||||
}>({
|
||||
isOpen: false,
|
||||
title: "",
|
||||
message: ""
|
||||
});
|
||||
|
||||
/**
|
||||
* Onglet resté ouvert pendant un déploiement : les identifiants de Server
|
||||
* Action ont changé côté serveur. On propose le rechargement au lieu
|
||||
* d'afficher une erreur technique incompréhensible.
|
||||
*/
|
||||
const modalErreur = (err: unknown, titre: string, message: string) =>
|
||||
isStaleServerActionError(err)
|
||||
? {
|
||||
isOpen: true,
|
||||
title: "Page à recharger",
|
||||
message: STALE_ACTION_MESSAGE,
|
||||
variant: "error" as const,
|
||||
action: { label: "Recharger la page", onClick: () => window.location.reload() },
|
||||
}
|
||||
: { isOpen: true, title: titre, message, variant: "error" as const };
|
||||
|
||||
const [confirmModal, setConfirmModal] = useState<{
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
@@ -59,7 +82,7 @@ export function SnapshotList({ type }: SnapshotListProps) {
|
||||
const data = await getSnapshots(type);
|
||||
setSnapshots(data);
|
||||
} catch (err) {
|
||||
setError("Impossible de charger les données.");
|
||||
setError(isStaleServerActionError(err) ? STALE_ACTION_MESSAGE : "Impossible de charger les données.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -83,10 +106,10 @@ export function SnapshotList({ type }: SnapshotListProps) {
|
||||
setSnapshots(prev => prev.filter(s => s.id !== id));
|
||||
setModal({ isOpen: true, title: "Action Réussie", message: "L'élément a été supprimé de votre historique." });
|
||||
} else {
|
||||
setModal({ isOpen: true, title: "Erreur", message: "Impossible de supprimer l'élément." });
|
||||
setModal({ isOpen: true, title: "Erreur", message: "Impossible de supprimer l'élément.", variant: "error" });
|
||||
}
|
||||
} catch (err) {
|
||||
setModal({ isOpen: true, title: "Erreur Technique", message: "Une erreur est survenue lors de la suppression." });
|
||||
setModal(modalErreur(err, "Erreur Technique", "Une erreur est survenue lors de la suppression."));
|
||||
} finally {
|
||||
setIsDeleting(null);
|
||||
}
|
||||
@@ -215,6 +238,8 @@ export function SnapshotList({ type }: SnapshotListProps) {
|
||||
onClose={() => setModal(prev => ({ ...prev, isOpen: false }))}
|
||||
title={modal.title}
|
||||
message={modal.message}
|
||||
variant={modal.variant}
|
||||
action={modal.action}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
|
||||
Reference in new issue
Block a user