feat(pos-selfpay-flow): pOS selfpay: simplified pay flow with optional email receipt

This commit is contained in:
Deploy
2026-08-26 20:40:43 +02:00
parent 19f37b70e1
commit 3ccbf9f450
9 changed files with 212 additions and 27 deletions

View File

@@ -140,6 +140,9 @@ export default function RegisterPage() {
// POS-FIX-4: toast notification when product added to cart
const [addedToast, setAddedToast] = useState<string | null>(null);
// POS-SELFPAY-FLOW: optional email for receipt before payment
const [showSelfpayEmail, setShowSelfpayEmail] = useState(false);
const [selfpayEmail, setSelfpayEmail] = useState('');
let toastTimer: ReturnType<typeof setTimeout> | undefined;
const showAddedToast = (name: string) => {
clearTimeout(toastTimer);
@@ -1645,19 +1648,22 @@ export default function RegisterPage() {
{error}
</p>
)}
<div className="mt-3 grid grid-cols-2 gap-2">
{config.paymentMethods.map((method) => (
<button
key={method.id}
type="button"
onClick={() => setPaymentMethod(method)}
disabled={cart.length === 0 || processing || remainingCents === 0}
className={`min-h-14 rounded-xl px-2 font-bold text-white disabled:opacity-40 ${method.kind === 'cash' ? 'bg-green-600' : method.kind === 'card' ? 'bg-blue-600' : 'bg-slate-700'}`}
>
{method.kind === 'cash' ? '💵' : method.kind === 'card' ? '💳' : '◉'} {method.label}
</button>
))}
</div>
{/* POS-SELFPAY-FLOW: in selfpay mode, hide payment methods until email is captured */}
{(!isSelfpayMode || selfpayEmail !== undefined) && (
<div className="mt-3 grid grid-cols-2 gap-2">
{config.paymentMethods.map((method) => (
<button
key={method.id}
type="button"
onClick={() => setPaymentMethod(method)}
disabled={cart.length === 0 || processing || remainingCents === 0}
className={`min-h-14 rounded-xl px-2 font-bold text-white disabled:opacity-40 ${method.kind === 'cash' ? 'bg-green-600' : method.kind === 'card' ? 'bg-blue-600' : 'bg-slate-700'}`}
>
{method.kind === 'cash' ? '💵' : method.kind === 'card' ? '💳' : '◉'} {method.label}
</button>
))}
</div>
)}
<div className="mt-3 grid grid-cols-2 gap-2">
{!isSelfpayMode && (
<button
@@ -1671,19 +1677,20 @@ export default function RegisterPage() {
)}
<button
type="button"
onClick={() => void confirmSale()}
onClick={() =>
isSelfpayMode
? setShowSelfpayEmail(true)
: void confirmSale()
}
disabled={
processing ||
cart.length === 0 ||
payments.length === 0 ||
paidCents <= 0 ||
paidCents > totals.total
!isSelfpayMode &&
(processing || cart.length === 0 || payments.length === 0 || paidCents <= 0 || paidCents > totals.total)
}
className={`min-h-16 rounded-xl bg-[#1B4332] text-lg font-bold text-white disabled:opacity-40 ${
isSelfpayMode ? 'col-span-2' : ''
}`}
>
{processing ? 'Confirmando…' : 'Cobrar e imprimir'}
{processing ? 'Confirmando…' : isSelfpayMode ? '💳 Pagar' : 'Cobrar e imprimir'}
</button>
</div>
<button
@@ -1713,6 +1720,81 @@ export default function RegisterPage() {
</div>
)}
{showFreeItem && <FreeItemModal onAdd={addFreeItem} onClose={() => setShowFreeItem(false)} />}
{/* POS-SELFPAY-FLOW: optional email modal before payment */}
{showSelfpayEmail && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
role="dialog"
aria-modal="true"
aria-labelledby="selfpay-email-title"
>
<div className="w-full max-w-sm space-y-5 rounded-2xl bg-white p-6 shadow-2xl">
<div className="flex items-start justify-between gap-4">
<div>
<p className="text-sm font-semibold text-[#2D6A4F]">
Total {formatPrice(totals.total)}
</p>
<h2 id="selfpay-email-title" className="text-2xl font-bold text-gray-900">
Recibir ticket por email
</h2>
<p className="mt-1 text-sm text-gray-500">
¿Quieres recibir el ticket de compra?
</p>
</div>
<button
type="button"
onClick={() => {
setSelfpayEmail('');
setShowSelfpayEmail(false);
}}
className="min-h-12 min-w-12 rounded-xl bg-gray-100 text-xl"
aria-label="Cerrar"
>
</button>
</div>
<form
onSubmit={(e) => {
e.preventDefault();
setShowSelfpayEmail(false);
}}
className="space-y-4"
>
<div>
<label htmlFor="selfpay-email" className="mb-1 block text-sm font-medium text-gray-700">
Email (opcional)
</label>
<input
id="selfpay-email"
type="email"
value={selfpayEmail}
onChange={(e) => setSelfpayEmail(e.target.value)}
placeholder="tu@email.com"
className="w-full rounded-xl border border-gray-300 px-4 py-3 text-sm outline-none focus:border-[#2D6A4F]"
/>
</div>
<button
type="submit"
className="w-full rounded-xl bg-[#2D6A4F] py-3 font-bold text-white"
>
Continuar al pago
</button>
<button
type="button"
onClick={() => {
setSelfpayEmail('');
setShowSelfpayEmail(false);
}}
className="w-full rounded-xl border border-gray-300 py-2 text-sm font-medium text-gray-500"
>
Omitir seguir sin email
</button>
</form>
</div>
</div>
)}
{paymentMethod && (
<PaymentModal
method={paymentMethod}