fix(pos): handle expired API sessions and add receipt preview close button

This commit is contained in:
Deploy
2026-08-26 07:05:02 +02:00
parent b6af852b54
commit 290ced173b
14 changed files with 52 additions and 17 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@mercadodevida/admin",
"version": "0.2.8",
"version": "0.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mercadodevida/admin",
"version": "0.2.8",
"version": "0.2.9",
"dependencies": {
"@lexical/history": "^0.49.0",
"@lexical/html": "^0.49.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@mercadodevida/admin",
"version": "0.2.8",
"version": "0.2.9",
"private": true,
"scripts": {
"dev": "next dev --port 3001",

View File

@@ -1,12 +1,12 @@
{
"name": "mercadodevida-pos",
"version": "0.2.8",
"version": "0.2.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mercadodevida-pos",
"version": "0.2.8",
"version": "0.2.9",
"dependencies": {
"next": "^16.3.1",
"react": "^19.2.8",

View File

@@ -1,6 +1,6 @@
{
"name": "mercadodevida-pos",
"version": "0.2.8",
"version": "0.2.9",
"private": true,
"scripts": {
"dev": "next dev --port 3002",

View File

@@ -59,7 +59,15 @@ export default function ReceiptModal({
aria-modal="true"
aria-labelledby="receipt-title"
>
<div className="mx-auto w-full max-w-xl rounded-2xl bg-white p-6 shadow-2xl print:max-w-none print:rounded-none print:p-0 print:shadow-none">
<div className="relative mx-auto w-full max-w-xl rounded-2xl bg-white p-6 shadow-2xl print:max-w-none print:rounded-none print:p-0 print:shadow-none">
<button
type="button"
onClick={onDelivered}
aria-label="Cerrar"
className="no-print absolute right-4 top-4 text-2xl text-gray-400 hover:text-gray-700"
>
</button>
{/* Ticket content - only this gets printed */}
<div ref={articleRef} className="ticket-print-area">
<article className="space-y-4 bg-white text-sm text-gray-900 print:bg-white">

View File

@@ -12,6 +12,15 @@ async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
...(init?.headers ?? {}),
},
});
if (res.status === 401) {
if (typeof window !== 'undefined') {
window.location.href = '/login';
}
throw Object.assign(new Error('Sesión caducada, identifícate de nuevo'), {
status: 401,
code: 'UNAUTHORIZED',
});
}
if (!res.ok) {
const err = (await res.json().catch(() => ({ message: res.statusText }))) as {
message?: string;
@@ -22,6 +31,15 @@ async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
code: err.error?.code,
});
}
// Defensive: if a proxy/misconfiguration ever returns HTML on a successful
// response, surface a readable message instead of a JSON parse crash.
const contentType = res.headers.get('content-type') ?? '';
if (!contentType.includes('application/json')) {
throw Object.assign(new Error('Respuesta inesperada del servidor (sesión o proxy)'), {
status: res.status,
code: 'INVALID_RESPONSE',
});
}
return res.json() as Promise<T>;
}

View File

@@ -20,6 +20,15 @@ export function proxy(request: NextRequest) {
cookie.includes('session_token=');
if (!hasSession) {
// API callers expect JSON. Redirecting them to /login makes fetch() follow
// the redirect and receive the HTML login page, which surfaces as the
// cryptic "Unexpected token '<'" parse error in the terminal UI.
if (pathname.startsWith('/api/')) {
return NextResponse.json(
{ error: { statusCode: 401, code: 'UNAUTHORIZED', message: 'Authentication required' } },
{ status: 401 },
);
}
return NextResponse.redirect(new URL('/login', request.url));
}