From bfbbfb9c09e8592358f5525ec5ab98a9087b37fc Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 3 Jul 2026 11:27:53 -0300 Subject: [PATCH] feat(checkout): enhance checkout page with payment methods and form validation --- .../checkout-page.component.html | 369 ++++++++++++------ .../checkout-page.component.scss | 281 ++++++++++++- .../checkout-page/checkout-page.component.ts | 43 +- 3 files changed, 558 insertions(+), 135 deletions(-) diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.html b/src/app/features/store/pages/checkout-page/checkout-page.component.html index 08a46f9..729348f 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.html +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.html @@ -1,137 +1,264 @@
-
+
- -
- +
+ - - -
-
+ + +
+
+ + + @if (getControlError('nombre'); as errorMessage) { + {{ errorMessage }} + } +
- -
- - - @if (getControlError('nombre'); as errorMessage) { - {{ errorMessage }} - } +
+ + + @if (getControlError('email'); as errorMessage) { + {{ errorMessage }} + } +
+ +
+ + + @if (getControlError('dni'); as errorMessage) { + {{ errorMessage }} + } +
+ +
+ + + @if (getControlError('telefono'); as errorMessage) { + {{ errorMessage }} + } +
- -
- - - @if (getControlError('email'); as errorMessage) { - {{ errorMessage }} - } +
+ + Cancelar + + + Continuar + +
+ + + + +
+
+
+

Selecciona el metodo de pago

+ +
+ @for (method of paymentMethods; track method.id) { + + } +
+
+ +
+ @if (selectedPaymentMethod() === 'qr') { +
+

Ingresa a tu billetera y escanea el siguiente QR

+
+ +
+ + + +
+
+ } @else if (selectedPaymentMethod() === 'transferencia') { +
+

Transferi a la siguiente cuenta desde cualquier billetera

+
+ + +
+ } @else { +
+

Descarga la app de TelePagos para finalizar la compra

+
+ +
+
+ + + Disponible en + Google Play + +
+ +
+ + + Descargalo en + App Store + +
+
+
+ } +
- -
- - - @if (getControlError('dni'); as errorMessage) { - {{ errorMessage }} - } -
+
+ + Cancelar + - -
- - - @if (getControlError('telefono'); as errorMessage) { - {{ errorMessage }} - } + + Finalizar compra +
-
+ - -
- - Cancelar - - - Continuar - -
+ +
- -
+
+ +
- - -
-

Próximamente: sección de pago.

-
-
- -
- - -
- -
- -
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.scss b/src/app/features/store/pages/checkout-page/checkout-page.component.scss index b7dbfbf..97c3087 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.scss +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.scss @@ -1,6 +1,6 @@ .checkout-page { display: grid; - grid-template-columns: 1fr 420px; + grid-template-columns: minmax(0, 1fr) 420px; gap: 2rem; align-items: start; padding: 2rem 0; @@ -9,13 +9,12 @@ grid-template-columns: 1fr; } - // ── Left column ────────────────────────────────────────────────── &__stepper-col { + min-width: 0; border-radius: 4px; min-height: 420px; } - // ── Right column ───────────────────────────────────────────────── &__cart-col { border: 1px solid #ececec; border-radius: 4px; @@ -27,7 +26,6 @@ } } -// ── Form ──────────────────────────────────────────────────────────── .checkout-form { display: flex; flex-direction: column; @@ -43,6 +41,14 @@ grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 0.5rem; + + @media (max-width: 540px) { + grid-template-columns: 1fr; + } + } + + &__actions--payment { + margin-top: 2rem; } &__action-btn { @@ -51,7 +57,6 @@ } } -// ── Field label & error ────────────────────────────────────────────── .form-field { display: grid; gap: 0.35rem; @@ -60,7 +65,6 @@ font-size: 0.8rem; font-weight: 600; color: var(--bs-secondary, #6c757d); - text-transform: none; letter-spacing: 0; } @@ -75,12 +79,265 @@ } } -// ── Payment placeholder ────────────────────────────────────────────── -.checkout-payment-placeholder { +.checkout-payment { + display: grid; + gap: 1rem; + + &__content { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(280px, 330px); + gap: 2rem; + align-items: start; + + @media (max-width: 760px) { + grid-template-columns: 1fr; + } + } +} + +.payment-methods { + min-width: 0; + + &__title { + margin: 0 0 1.75rem; + color: #8f8f8f; + font-size: 0.95rem; + font-weight: 700; + } + + &__list { + display: grid; + gap: 1.1rem; + } +} + +.payment-method { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 0.85rem; + padding: 0.35rem 0; + color: #666666; + cursor: pointer; + transition: color 0.2s ease; + + &:hover { + color: #4f4f4f; + } + + &.is-selected { + color: var(--tenant-primary, #6376f3); + } + + &__radio { + width: 16px; + height: 16px; + margin: 0; + accent-color: var(--tenant-primary, #6376f3); + cursor: pointer; + } + + &__label { + min-width: 0; + font-size: 1rem; + font-weight: 700; + line-height: 1.2; + } + + &__chevron { + color: #9f9f9f; + font-size: 0.95rem; + opacity: 0; + transform: translateX(-4px); + transition: opacity 0.2s ease, transform 0.2s ease; + } + + &.is-selected &__chevron, + &:hover &__chevron { + opacity: 1; + transform: translateX(0); + } +} + +.telepagos-logo { + display: inline-block; + font-size: 1.45rem; + font-weight: 800; + line-height: 1; + letter-spacing: -0.03em; + background: linear-gradient(90deg, #0a69d8 0 78%, #f6a11a 78% 100%); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.payment-panel { + min-width: 0; + + &__card { + min-height: 260px; + padding: 1.5rem 1.75rem; + border-radius: 0.5rem; + background: #ffffff; + box-shadow: 0 0 0 1px #f1f1f1; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + } + + &__title { + max-width: 18rem; + margin: 0; + color: #8b8b8b; + font-size: 1rem; + font-weight: 700; + line-height: 1.35; + } + + &__divider { + width: 100%; + max-width: 220px; + height: 1px; + margin: 1.2rem 0 1.45rem; + background: #e7e7e7; + } + + &__account { + width: 100%; + display: grid; + gap: 0.75rem; + color: #7a7a7a; + } + + &__eyebrow { + margin: 0; + color: #838383; + font-size: 0.95rem; + letter-spacing: 0.06em; + } +} + +.payment-detail { + display: grid; + grid-template-columns: auto auto; + justify-content: center; + gap: 0.35rem; + align-items: center; + font-size: 0.98rem; + + &--copy { + grid-template-columns: auto auto auto; + } + + &__label { + color: #8a8a8a; + } + + &__value { + color: #5e5e5e; + font-weight: 700; + } + + &__feedback { + display: block; + margin-top: -0.25rem; + color: var(--tenant-primary, #6376f3); + font-size: 0.78rem; + font-weight: 700; + } +} + +.qr-code { + position: relative; + width: min(170px, 100%); + aspect-ratio: 1; + border: 8px solid #ffffff; + background-color: #ffffff; + background-image: + linear-gradient( + 90deg, + rgba(17, 17, 17, 0.95) 0 14%, + transparent 14% 28%, + rgba(17, 17, 17, 0.95) 28% 42%, + transparent 42% 56%, + rgba(17, 17, 17, 0.95) 56% 70%, + transparent 70% 84%, + rgba(17, 17, 17, 0.95) 84% 100% + ), + linear-gradient( + rgba(17, 17, 17, 0.95) 0 14%, + transparent 14% 28%, + rgba(17, 17, 17, 0.95) 28% 42%, + transparent 42% 56%, + rgba(17, 17, 17, 0.95) 56% 70%, + transparent 70% 84%, + rgba(17, 17, 17, 0.95) 84% 100% + ); + background-size: 18px 18px; + background-position: 0 0, 9px 9px; +} + +.qr-code__finder { + position: absolute; + width: 38px; + height: 38px; + border: 5px solid #111111; + background: #ffffff; + box-shadow: inset 0 0 0 8px #ffffff, inset 0 0 0 14px #111111; + + &--tl { + top: 10px; + left: 10px; + } + + &--tr { + top: 10px; + right: 10px; + } + + &--bl { + bottom: 10px; + left: 10px; + } +} + +.store-badges { + width: 100%; + max-width: 210px; + display: grid; + gap: 0.8rem; +} + +.store-badge { display: flex; align-items: center; - justify-content: center; - padding: 3rem 1rem; - color: var(--bs-secondary, #6c757d); - font-size: 0.9rem; + gap: 0.75rem; + padding: 0.75rem 0.95rem; + border-radius: 0.85rem; + background: #111111; + color: #ffffff; + box-shadow: 0 10px 24px rgba(17, 17, 17, 0.16); + + i { + font-size: 1.35rem; + } + + &__text { + display: grid; + text-align: left; + line-height: 1.1; + + small { + font-size: 0.62rem; + letter-spacing: 0.05em; + text-transform: uppercase; + opacity: 0.8; + } + + strong { + font-size: 1rem; + font-weight: 700; + } + } } diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.ts index 2577fdc..88ae262 100644 --- a/src/app/features/store/pages/checkout-page/checkout-page.component.ts +++ b/src/app/features/store/pages/checkout-page/checkout-page.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, computed, inject, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject, signal, ViewChild } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { Router } from '@angular/router'; @@ -9,6 +9,10 @@ import { StepperComponent } from '../../../../shared/components/stepper/stepper. import { StepComponent } from '../../../../shared/components/stepper/step.component'; import { InputComponent } from '../../../../shared/components/input/input.component'; import { ButtonComponent } from '../../../../shared/components/button/button.component'; +import { IconButtonComponent } from '../../../../shared/components/icon-button/icon-button.component'; + +type PaymentMethod = 'qr' | 'transferencia' | 'telepagos'; +type TransferField = 'cvu' | 'alias'; @Component({ selector: 'app-checkout-page', @@ -19,7 +23,8 @@ import { ButtonComponent } from '../../../../shared/components/button/button.com StepperComponent, StepComponent, InputComponent, - ButtonComponent + ButtonComponent, + IconButtonComponent ], templateUrl: './checkout-page.component.html', styleUrl: './checkout-page.component.scss', @@ -55,6 +60,19 @@ export class CheckoutPageComponent { }); protected readonly isStep1Valid = computed(() => this.form.valid); + protected readonly paymentMethods: ReadonlyArray<{ id: PaymentMethod; label: string }> = [ + { id: 'qr', label: 'QR' }, + { id: 'transferencia', label: 'Transferencia' }, + { id: 'telepagos', label: 'TelePagos' } + ]; + protected readonly selectedPaymentMethod = signal('qr'); + protected readonly copiedTransferField = signal(null); + protected readonly transferAccount = { + titular: 'Nombre y Apellido', + entidad: 'TelePagos', + cvu: '0000000000000000000000', + alias: 'telepagos.ar' + }; private mapCartItemToMock(item: CartItem): CartItemMock { const fullName = item.product?.nombre ?? ''; @@ -114,4 +132,25 @@ export class CheckoutPageComponent { protected onCancel(): void { void this.router.navigate(['/']); } + + protected selectPaymentMethod(method: PaymentMethod): void { + this.selectedPaymentMethod.set(method); + } + + protected async copyTransferValue(field: TransferField, value: string): Promise { + if (!globalThis.navigator?.clipboard?.writeText) return; + + try { + await globalThis.navigator.clipboard.writeText(value); + this.copiedTransferField.set(field); + + setTimeout(() => { + if (this.copiedTransferField() === field) { + this.copiedTransferField.set(null); + } + }, 1800); + } catch { + this.copiedTransferField.set(null); + } + } }