fix(storefront+TPV): storefront live search proxy, product expiry/weight display, TPV receipt popup timestamp
This commit is contained in:
@@ -64,6 +64,11 @@ export default function ReceiptModal({
|
|||||||
<div ref={articleRef} className="ticket-print-area">
|
<div ref={articleRef} className="ticket-print-area">
|
||||||
<article className="space-y-4 text-sm text-gray-900">
|
<article className="space-y-4 text-sm text-gray-900">
|
||||||
<header className="border-b border-dashed border-gray-400 pb-4 text-center">
|
<header className="border-b border-dashed border-gray-400 pb-4 text-center">
|
||||||
|
{/* Timestamp */}
|
||||||
|
<div className="flex justify-between items-center mb-2 text-xs text-gray-500">
|
||||||
|
<span>Ticket #{receipt.receiptNumber}</span>
|
||||||
|
<span>{new Date(receipt.createdAt).toLocaleString('es-ES', { dateStyle: 'short', timeStyle: 'short' })}</span>
|
||||||
|
</div>
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<div className="mb-3 flex justify-center">
|
<div className="mb-3 flex justify-center">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* Add twitter_url and pinterest_url columns to store_settings table.
|
* Add twitter_url and pinterest_url columns to store_settings table.
|
||||||
* Usage: node --env-file=.env -e "require('./migrations/062_settings_twitter_pinterest.js')"
|
|
||||||
*/
|
*/
|
||||||
import { query } from '../src/infrastructure/db/migrate.js';
|
|
||||||
|
|
||||||
async function migrate() {
|
/** @param {import('pg-migrate').MigrationBuilder} pgm */
|
||||||
await query(`ALTER TABLE store_settings ADD COLUMN IF NOT EXISTS twitter_url TEXT`);
|
export const up = (pgm) => {
|
||||||
await query(`ALTER TABLE store_settings ADD COLUMN IF NOT EXISTS pinterest_url TEXT`);
|
pgm.addColumn('store_settings', {
|
||||||
console.log('✓ Added twitter_url and pinterest_url to store_settings');
|
twitter_url: { type: 'text', notNull: false, default: null },
|
||||||
}
|
pinterest_url: { type: 'text', notNull: false, default: null },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
migrate().catch(e => { console.error(e); process.exit(1); });
|
/** @param {import('pg-migrate').MigrationBuilder} pgm */
|
||||||
|
export const down = (pgm) => {
|
||||||
|
pgm.dropColumn('store_settings', 'twitter_url');
|
||||||
|
pgm.dropColumn('store_settings', 'pinterest_url');
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const path = req.nextUrl.pathname.replace('/api/catalog-proxy/', '');
|
||||||
|
const cookies = req.headers.get('cookie') ?? '';
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API}/${path}${req.nextUrl.search}`, {
|
||||||
|
headers: { accept: 'application/json', cookie: cookies },
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
return NextResponse.json(data, { status: res.status });
|
||||||
|
} catch (err) {
|
||||||
|
return NextResponse.json({ error: String(err) }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
const path = req.nextUrl.pathname.replace('/api/catalog-proxy/', '');
|
||||||
|
const cookies = req.headers.get('cookie') ?? '';
|
||||||
|
const body = await req.text();
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API}/${path}${req.nextUrl.search}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', cookie: cookies },
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
return NextResponse.json(data, { status: res.status });
|
||||||
|
} catch (err) {
|
||||||
|
return NextResponse.json({ error: String(err) }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,6 +102,18 @@ export default async function ProductPage({ params }: PageProps) {
|
|||||||
<dt className="font-semibold text-emerald-950">Estado</dt>
|
<dt className="font-semibold text-emerald-950">Estado</dt>
|
||||||
<dd>{product.state}</dd>
|
<dd>{product.state}</dd>
|
||||||
</div>
|
</div>
|
||||||
|
{product.expirationDate && (
|
||||||
|
<div>
|
||||||
|
<dt className="font-semibold text-emerald-950">Caducidad</dt>
|
||||||
|
<dd>{new Date(product.expirationDate).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' })}</dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{product.unitWeightKg && (
|
||||||
|
<div>
|
||||||
|
<dt className="font-semibold text-emerald-950">Peso</dt>
|
||||||
|
<dd>{product.unitWeightKg >= 1 ? `${product.unitWeightKg} kg` : `${Math.round(product.unitWeightKg * 1000)} g`}</dd>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</dl>
|
</dl>
|
||||||
<AddToCart
|
<AddToCart
|
||||||
productId={product.id}
|
productId={product.id}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ export interface ProductSummaryDto {
|
|||||||
seoDescription: string | null;
|
seoDescription: string | null;
|
||||||
categoryIds: string[];
|
categoryIds: string[];
|
||||||
brandId: string | null;
|
brandId: string | null;
|
||||||
|
expirationDate?: string | null;
|
||||||
|
unitWeightKg?: number | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
@@ -79,7 +81,11 @@ export interface SearchProductsInput {
|
|||||||
|
|
||||||
// Use env var if set, otherwise use relative URL (works for SSR and client-side via same-origin)
|
// Use env var if set, otherwise use relative URL (works for SSR and client-side via same-origin)
|
||||||
function apiBaseUrl(): string {
|
function apiBaseUrl(): string {
|
||||||
const base = process.env.API_BASE_URL ?? process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
|
// Browser: use same-origin proxy to avoid network/IP issues
|
||||||
|
// SSR: use backend URL directly
|
||||||
|
const base = (typeof window !== 'undefined')
|
||||||
|
? '/api/catalog-proxy'
|
||||||
|
: (process.env.API_BASE_URL ?? process.env.NEXT_PUBLIC_API_BASE_URL ?? 'http://127.0.0.1:3000');
|
||||||
return base.replace(/\/$/, '');
|
return base.replace(/\/$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"feature_id": "ADMIN-UI-FIXES2",
|
"feature_id": "FRONTEND-UI-FIXES2",
|
||||||
"stage": "build",
|
"stage": "build",
|
||||||
"agent": "implementer",
|
"agent": "implementer",
|
||||||
"action": "",
|
"action": "",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"next_agent": "leader",
|
"next_agent": "leader",
|
||||||
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
"waiting_for": "Seleccionar una feature pending y actualizar este estado",
|
||||||
"updated_at": "2026-08-24T20:06:08Z",
|
"updated_at": "2026-08-24T20:09:36Z",
|
||||||
"timeline": [
|
"timeline": [
|
||||||
{
|
{
|
||||||
"ts": "2026-08-24T20:06:08Z",
|
"ts": "2026-08-24T20:06:08Z",
|
||||||
@@ -14,6 +14,13 @@
|
|||||||
"stage": "build",
|
"stage": "build",
|
||||||
"state": "running",
|
"state": "running",
|
||||||
"message": "Estado actualizado"
|
"message": "Estado actualizado"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-08-24T20:09:36Z",
|
||||||
|
"agent": "implementer",
|
||||||
|
"stage": "build",
|
||||||
|
"state": "running",
|
||||||
|
"message": "Estado actualizado"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user