feat(pos-selfpay-flow): pOS selfpay: simplified pay flow with optional email receipt
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user