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">
|
||||
<article className="space-y-4 text-sm text-gray-900">
|
||||
<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 */}
|
||||
<div className="mb-3 flex justify-center">
|
||||
<img
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
/**
|
||||
* 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() {
|
||||
await query(`ALTER TABLE store_settings ADD COLUMN IF NOT EXISTS twitter_url TEXT`);
|
||||
await query(`ALTER TABLE store_settings ADD COLUMN IF NOT EXISTS pinterest_url TEXT`);
|
||||
console.log('✓ Added twitter_url and pinterest_url to store_settings');
|
||||
}
|
||||
/** @param {import('pg-migrate').MigrationBuilder} pgm */
|
||||
export const up = (pgm) => {
|
||||
pgm.addColumn('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>
|
||||
<dd>{product.state}</dd>
|
||||
</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>
|
||||
<AddToCart
|
||||
productId={product.id}
|
||||
|
||||
@@ -53,6 +53,8 @@ export interface ProductSummaryDto {
|
||||
seoDescription: string | null;
|
||||
categoryIds: string[];
|
||||
brandId: string | null;
|
||||
expirationDate?: string | null;
|
||||
unitWeightKg?: number | null;
|
||||
createdAt: 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)
|
||||
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(/\/$/, '');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"feature_id": "ADMIN-UI-FIXES2",
|
||||
"feature_id": "FRONTEND-UI-FIXES2",
|
||||
"stage": "build",
|
||||
"agent": "implementer",
|
||||
"action": "",
|
||||
"state": "running",
|
||||
"next_agent": "leader",
|
||||
"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": [
|
||||
{
|
||||
"ts": "2026-08-24T20:06:08Z",
|
||||
@@ -14,6 +14,13 @@
|
||||
"stage": "build",
|
||||
"state": "running",
|
||||
"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