Avviso storefront per ordini gia' annullati/rimborsati (info non bloccante)

Al passo 2 del recesso, se l'ordine risulta gia' annullato (cancelledAt) o
rimborsato/voided (financialStatus), mostra un banner informativo ambra (non
blocca): il recesso resta ammesso (diritto incondizionato + funzione sempre
accessibile Art. 54-bis), ma il consumatore e' informato dello stato.

- recesso.copy: NOTICE.orderClosed.
- recesso.server: noticeBanner + campo notice in renderStep2 + stile .notice ambra con icona.
- proxy: calcola orderClosed al lookup e passa la notice a renderStep2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mv83a29B4eFv5ixoj6PoE1
This commit is contained in:
2026-07-07 15:46:51 +02:00
parent 3968d34011
commit 883bd82708
3 changed files with 37 additions and 1 deletions

View File

@@ -47,6 +47,12 @@ export const ERROR = {
generic: "Si è verificato un problema. Riprova tra poco.",
} as const;
// Avvisi informativi (non bloccanti).
export const NOTICE = {
orderClosed:
"Questo ordine risulta già annullato o rimborsato. Puoi comunque registrare il recesso; il negozio ti contatterà per eventuali dettagli.",
} as const;
// Schermata finale (dopo "Conferma recesso").
export function successMessage(
orderName: string,

View File

@@ -806,6 +806,21 @@ const PAGE_CSS = `
font-size: 0.92rem;
}
.error__icon { flex: none; width: 20px; height: 20px; margin-top: 1px; fill: currentColor; }
.notice {
display: flex;
align-items: flex-start;
gap: 10px;
background: rgba(240, 170, 40, 0.14);
border: 1px solid rgba(240, 170, 40, 0.55);
border-left: 3px solid rgba(240, 170, 40, 0.95);
border-radius: var(--radius-sm);
padding: 12px 14px;
margin: 0 0 18px;
color: var(--text);
font-size: 0.9rem;
line-height: 1.5;
}
.notice__icon { flex: none; width: 20px; height: 20px; margin-top: 1px; fill: #e0a020; }
/* Riepilogo (step 3) */
.summary { margin: 18px 0 4px; }
@@ -893,6 +908,14 @@ function errorBanner(message?: string): string {
</div>`;
}
function noticeBanner(message?: string): string {
if (!message) return "";
return `<div class="notice" role="status">
<svg class="notice__icon" viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M10 1.5a8.5 8.5 0 1 0 0 17 8.5 8.5 0 0 0 0-17ZM9 5h2v2H9V5Zm0 4h2v6H9V9Z"/></svg>
<span>${escapeHtml(message)}</span>
</div>`;
}
/**
* Pulsante info "i" + popover nativo (mini-modal, zero JS via Popover API).
* Il popover vive nel top-layer; chiusura con Esc, click fuori o pulsante.
@@ -985,12 +1008,13 @@ export function renderStep2(data: {
customerName?: string;
statementText: string;
error?: string;
notice?: string;
}): string {
const customerName = data.customerName ?? "";
return renderShell(
stepLayout({
head: stepHead(2, `Ordine ${data.orderName}`),
body: `${errorBanner(data.error)}
body: `${errorBanner(data.error)}${noticeBanner(data.notice)}
<form id="rcform" method="post" action="${PROXY_STOREFRONT_PATH}" novalidate>
<input type="hidden" name="intent" value="details">
<input type="hidden" name="orderId" value="${attr(data.orderId)}">

View File

@@ -24,6 +24,7 @@ import {
import {
ERROR,
EXCLUSION_REASON,
NOTICE,
exclusionMessage,
statementTemplate,
successMessage,
@@ -195,12 +196,17 @@ export const action = async ({ request }: ActionFunctionArgs) => {
);
}
const orderClosed =
!!match.cancelledAt ||
match.financialStatus === "REFUNDED" ||
match.financialStatus === "VOIDED";
return htmlResponse(
renderStep2({
orderId: match.orderId,
orderName: match.orderName,
email: match.email,
statementText: statementTemplate(match.orderName),
notice: orderClosed ? NOTICE.orderClosed : undefined,
}),
);
}