From 811f127deb5b8dda27b2e1f42f3ca703761b9bb4 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 3 Jul 2026 11:31:09 -0300 Subject: [PATCH] feat: implement checkout data and payment steps as standalone components - Add `CheckoutDataStepComponent` for handling user data input with reactive forms. - Create `CheckoutPaymentStepComponent` for managing payment method selection and display. - Refactor `CheckoutPageComponent` to utilize the new components, improving modularity and readability. - Introduce SCSS styles for both new components, ensuring consistent design with existing styles. - Update HTML structure in `checkout-page.component.html` to integrate the new components. - Define models for checkout forms and payment methods in `checkout-page.models.ts`. --- .../checkout-data-step.component.html | 95 ++++++ .../checkout-data-step.component.scss | 48 +++ .../checkout-data-step.component.ts | 47 +++ .../checkout-page.component.html | 255 +------------- .../checkout-page.component.scss | 316 ------------------ .../checkout-page/checkout-page.component.ts | 57 ++-- .../checkout-page/checkout-page.models.ts | 23 ++ .../checkout-payment-step.component.html | 142 ++++++++ .../checkout-payment-step.component.scss | 278 +++++++++++++++ .../checkout-payment-step.component.ts | 32 ++ 10 files changed, 698 insertions(+), 595 deletions(-) create mode 100644 src/app/features/store/pages/checkout-page/checkout-data-step.component.html create mode 100644 src/app/features/store/pages/checkout-page/checkout-data-step.component.scss create mode 100644 src/app/features/store/pages/checkout-page/checkout-data-step.component.ts create mode 100644 src/app/features/store/pages/checkout-page/checkout-page.models.ts create mode 100644 src/app/features/store/pages/checkout-page/checkout-payment-step.component.html create mode 100644 src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss create mode 100644 src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts diff --git a/src/app/features/store/pages/checkout-page/checkout-data-step.component.html b/src/app/features/store/pages/checkout-page/checkout-data-step.component.html new file mode 100644 index 0000000..3d11f62 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-data-step.component.html @@ -0,0 +1,95 @@ +
+
+
+ + + @if (getControlError('nombre'); as errorMessage) { + {{ errorMessage }} + } +
+ +
+ + + @if (getControlError('email'); as errorMessage) { + {{ errorMessage }} + } +
+ +
+ + + @if (getControlError('dni'); as errorMessage) { + {{ errorMessage }} + } +
+ +
+ + + @if (getControlError('telefono'); as errorMessage) { + {{ errorMessage }} + } +
+
+ +
+ + Cancelar + + + Continuar + +
+
diff --git a/src/app/features/store/pages/checkout-page/checkout-data-step.component.scss b/src/app/features/store/pages/checkout-page/checkout-data-step.component.scss new file mode 100644 index 0000000..940ef25 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-data-step.component.scss @@ -0,0 +1,48 @@ +.checkout-form { + display: flex; + flex-direction: column; + gap: 1.5rem; + + &__fields { + display: grid; + gap: 1.25rem; + } + + &__actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-top: 0.5rem; + + @media (max-width: 540px) { + grid-template-columns: 1fr; + } + } + + &__action-btn { + display: block; + width: 100%; + } +} + +.form-field { + display: grid; + gap: 0.35rem; + + &__label { + font-size: 0.8rem; + font-weight: 600; + color: var(--bs-secondary, #6c757d); + letter-spacing: 0; + } + + &__required { + color: var(--bs-danger, #dc3545); + margin-left: 2px; + } + + &__error { + font-size: 0.75rem; + color: var(--bs-danger, #dc3545); + } +} diff --git a/src/app/features/store/pages/checkout-page/checkout-data-step.component.ts b/src/app/features/store/pages/checkout-page/checkout-data-step.component.ts new file mode 100644 index 0000000..b2ee6c3 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-data-step.component.ts @@ -0,0 +1,47 @@ +import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { ButtonComponent } from '../../../../shared/components/button/button.component'; +import { InputComponent } from '../../../../shared/components/input/input.component'; +import { CheckoutForm } from './checkout-page.models'; + +@Component({ + selector: 'app-checkout-data-step', + standalone: true, + imports: [ReactiveFormsModule, InputComponent, ButtonComponent], + templateUrl: './checkout-data-step.component.html', + styleUrl: './checkout-data-step.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CheckoutDataStepComponent { + readonly form = input.required(); + readonly cancelStep = output(); + readonly continueStep = output(); + + protected updateField(field: keyof CheckoutForm['controls'], value: string | number): void { + this.form().controls[field].setValue(String(value)); + } + + protected showControlError(controlName: keyof CheckoutForm['controls']): boolean { + const control = this.form().controls[controlName]; + return control.invalid && control.touched; + } + + protected getControlError(controlName: keyof CheckoutForm['controls']): string | null { + const control = this.form().controls[controlName]; + if (!this.showControlError(controlName)) return null; + if (control.hasError('required')) return 'Este campo es obligatorio.'; + if (control.hasError('email')) return 'Ingresa un email valido.'; + return 'El valor ingresado no es valido.'; + } + + protected submit(): void { + const form = this.form(); + if (form.invalid) { + form.markAllAsTouched(); + return; + } + + this.continueStep.emit(); + } +} 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 729348f..46276d8 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,252 +1,26 @@
-
- -
-
-
- - - @if (getControlError('nombre'); as errorMessage) { - {{ errorMessage }} - } -
- -
- - - @if (getControlError('email'); as errorMessage) { - {{ errorMessage }} - } -
- -
- - - @if (getControlError('dni'); as errorMessage) { - {{ errorMessage }} - } -
- -
- - - @if (getControlError('telefono'); 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 - -
-
-
- } -
-
- -
- - Cancelar - - - - Finalizar compra - -
-
+
-
@@ -259,6 +33,5 @@ backgroundColor="transparent" />
-
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 97c3087..f15a8fa 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 @@ -25,319 +25,3 @@ flex-direction: column; } } - -.checkout-form { - display: flex; - flex-direction: column; - gap: 1.5rem; - - &__fields { - display: grid; - gap: 1.25rem; - } - - &__actions { - display: grid; - 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 { - display: block; - width: 100%; - } -} - -.form-field { - display: grid; - gap: 0.35rem; - - &__label { - font-size: 0.8rem; - font-weight: 600; - color: var(--bs-secondary, #6c757d); - letter-spacing: 0; - } - - &__required { - color: var(--bs-danger, #dc3545); - margin-left: 2px; - } - - &__error { - font-size: 0.75rem; - color: var(--bs-danger, #dc3545); - } -} - -.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; - 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 88ae262..44903cb 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,30 +1,26 @@ import { ChangeDetectionStrategy, Component, computed, inject, signal, ViewChild } from '@angular/core'; -import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { FormBuilder, Validators } from '@angular/forms'; import { Router } from '@angular/router'; +import { startWith } from 'rxjs'; import { CartService } from '../../../../core/services/cart/cart.service'; import { CartItem } from '../../../../core/services/cart/cart.interface'; import { CartComponent, CartItemMock } from '../../../../shared/components/cart/cart.component'; -import { StepperComponent } from '../../../../shared/components/stepper/stepper.component'; 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'; +import { StepperComponent } from '../../../../shared/components/stepper/stepper.component'; +import { CheckoutDataStepComponent } from './checkout-data-step.component'; +import { CheckoutPaymentStepComponent } from './checkout-payment-step.component'; +import { CheckoutForm, PaymentMethod, PaymentMethodOption, TransferAccount, TransferField } from './checkout-page.models'; @Component({ selector: 'app-checkout-page', standalone: true, imports: [ - ReactiveFormsModule, CartComponent, StepperComponent, StepComponent, - InputComponent, - ButtonComponent, - IconButtonComponent + CheckoutDataStepComponent, + CheckoutPaymentStepComponent ], templateUrl: './checkout-page.component.html', styleUrl: './checkout-page.component.scss', @@ -37,7 +33,7 @@ export class CheckoutPageComponent { @ViewChild(StepperComponent) stepper!: StepperComponent; - protected readonly form = this.formBuilder.nonNullable.group({ + protected readonly form: CheckoutForm = this.formBuilder.nonNullable.group({ nombre: ['', [Validators.required]], email: ['', [Validators.required, Validators.email]], dni: ['', [Validators.required]], @@ -59,21 +55,27 @@ export class CheckoutPageComponent { return cart.items.map((item) => this.mapCartItemToMock(item)); }); - protected readonly isStep1Valid = computed(() => this.form.valid); - protected readonly paymentMethods: ReadonlyArray<{ id: PaymentMethod; label: string }> = [ + protected readonly isStep1Valid = signal(this.form.valid); + protected readonly paymentMethods: ReadonlyArray = [ { 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 = { + protected readonly transferAccount: TransferAccount = { titular: 'Nombre y Apellido', entidad: 'TelePagos', cvu: '0000000000000000000000', alias: 'telepagos.ar' }; + constructor() { + this.form.statusChanges + .pipe(startWith(this.form.status)) + .subscribe(() => this.isStep1Valid.set(this.form.valid)); + } + private mapCartItemToMock(item: CartItem): CartItemMock { const fullName = item.product?.nombre ?? ''; let product = fullName; @@ -104,28 +106,7 @@ export class CheckoutPageComponent { }; } - protected updateField(field: keyof typeof this.form.controls, value: string | number): void { - this.form.controls[field].setValue(String(value)); - } - - protected showControlError(controlName: keyof typeof this.form.controls): boolean { - const control = this.form.controls[controlName]; - return control.invalid && control.touched; - } - - protected getControlError(controlName: keyof typeof this.form.controls): string | null { - const control = this.form.controls[controlName]; - if (!this.showControlError(controlName)) return null; - if (control.hasError('required')) return 'Este campo es obligatorio.'; - if (control.hasError('email')) return 'Ingresá un email válido.'; - return 'El valor ingresado no es válido.'; - } - - protected onContinue(): void { - if (this.form.invalid) { - this.form.markAllAsTouched(); - return; - } + protected onStep1Continue(): void { this.stepper.next(); } diff --git a/src/app/features/store/pages/checkout-page/checkout-page.models.ts b/src/app/features/store/pages/checkout-page/checkout-page.models.ts new file mode 100644 index 0000000..ea06f64 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-page.models.ts @@ -0,0 +1,23 @@ +import { FormControl, FormGroup } from '@angular/forms'; + +export type PaymentMethod = 'qr' | 'transferencia' | 'telepagos'; +export type TransferField = 'cvu' | 'alias'; + +export interface PaymentMethodOption { + id: PaymentMethod; + label: string; +} + +export interface TransferAccount { + titular: string; + entidad: string; + cvu: string; + alias: string; +} + +export type CheckoutForm = FormGroup<{ + nombre: FormControl; + email: FormControl; + dni: FormControl; + telefono: FormControl; +}>; diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html new file mode 100644 index 0000000..54ae91d --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.html @@ -0,0 +1,142 @@ +
+
+
+

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 + +
+
+
+ } +
+
+ +
+ + Cancelar + + + + Finalizar compra + +
+
diff --git a/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss new file mode 100644 index 0000000..9642100 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.scss @@ -0,0 +1,278 @@ +.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; + } + } + + &__actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-top: 2rem; + + @media (max-width: 540px) { + grid-template-columns: 1fr; + } + } + + &__action-btn { + display: block; + width: 100%; + } +} + +.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; + 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-payment-step.component.ts b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts new file mode 100644 index 0000000..eb31710 --- /dev/null +++ b/src/app/features/store/pages/checkout-page/checkout-payment-step.component.ts @@ -0,0 +1,32 @@ +import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; + +import { ButtonComponent } from '../../../../shared/components/button/button.component'; +import { IconButtonComponent } from '../../../../shared/components/icon-button/icon-button.component'; +import { PaymentMethod, PaymentMethodOption, TransferAccount, TransferField } from './checkout-page.models'; + +@Component({ + selector: 'app-checkout-payment-step', + standalone: true, + imports: [ButtonComponent, IconButtonComponent], + templateUrl: './checkout-payment-step.component.html', + styleUrl: './checkout-payment-step.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CheckoutPaymentStepComponent { + readonly paymentMethods = input.required>(); + readonly selectedPaymentMethod = input.required(); + readonly copiedTransferField = input(null); + readonly transferAccount = input.required(); + + readonly paymentMethodChange = output(); + readonly copyTransferValue = output<{ field: TransferField; value: string }>(); + readonly cancelStep = output(); + + protected selectPaymentMethod(method: PaymentMethod): void { + this.paymentMethodChange.emit(method); + } + + protected requestCopy(field: TransferField, value: string): void { + this.copyTransferValue.emit({ field, value }); + } +}