Compare commits

..

6 Commits

9 changed files with 171 additions and 10 deletions

View File

@@ -186,9 +186,61 @@ describe('ReutilizablesTestPageComponent', () => {
).toContain('Todavia no se abrio ningun modal.');
confirmButton.click();
fixture.detectChanges();
deleteButton.click();
fixture.detectChanges();
expect(modalServiceStub.openConfirm).toHaveBeenCalled();
expect(modalServiceStub.openConfirmDelete).toHaveBeenCalled();
expect(modalServiceStub.openConfirm).toHaveBeenCalledWith({
title: 'Confirmar accion',
content: 'Caso base para verificar apertura, cierre y devolucion de resultado.',
confirmLabel: 'Confirmar'
});
expect(modalServiceStub.openConfirmDelete).toHaveBeenCalledWith({
title: 'Eliminar producto',
content:
'Esta accion eliminara el producto del catalogo. Podras volver a crearlo manualmente.',
confirmLabel: 'Eliminar'
});
expect(
element.querySelector('[data-testid="modal-last-result"]')?.textContent
).toContain('Resultado: false');
});
it('updates the visible result when the confirm modal resolves to true', async () => {
const modalServiceStub = createModalServiceStub();
await TestBed.configureTestingModule({
imports: [ReutilizablesTestPageComponent],
providers: [
{
provide: TenantService,
useValue: createTenantServiceStub(tenant)
},
{
provide: ToastService,
useValue: createToastServiceStub()
},
{
provide: ModalService,
useValue: modalServiceStub
}
]
}).compileComponents();
const fixture = TestBed.createComponent(ReutilizablesTestPageComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const confirmButton = Array.from(
element.querySelectorAll('.button-group app-button button')
).find((button) =>
button.textContent?.includes('Abrir confirm modal')
) as HTMLButtonElement;
confirmButton.click();
fixture.detectChanges();
expect(
element.querySelector('[data-testid="modal-last-result"]')?.textContent
).toContain('Resultado: true');
});
});

View File

@@ -198,14 +198,11 @@ export class ReutilizablesTestPageComponent {
}
protected openConfirmDeleteModal(): void {
this.modalService.openConfirmDelete({
this.openConfirmDelete({
title: 'Eliminar producto',
content:
'Esta accion eliminara el producto del catalogo. Podras volver a crearlo manualmente.',
confirmLabel: 'Eliminar',
cancelLabel: 'Conservar'
}).subscribe((confirmed) => {
this.lastModalResult = `Resultado: ${confirmed}`;
confirmLabel: 'Eliminar'
});
}
@@ -234,4 +231,12 @@ export class ReutilizablesTestPageComponent {
this.lastModalResult = `Resultado: ${confirmed}`;
});
}
private openConfirmDelete(
config: Parameters<ModalService['openConfirmDelete']>[0]
): void {
this.modalService.openConfirmDelete(config).subscribe((confirmed) => {
this.lastModalResult = `Resultado: ${confirmed}`;
});
}
}

View File

@@ -0,0 +1,67 @@
<section class="py-4 py-md-5">
<div class="container-xl px-3 px-md-4">
<div class="row justify-content-center py-md-4">
<div class="col-12 col-md-9 col-lg-7 col-xl-5">
<article class="card border-0 shadow-lg rounded-4">
<div class="card-body p-4 p-md-5">
<header class="text-center mb-4">
<h1 id="login-title" class="fw-bold text-uppercase mb-0" style="font-size: 17px; color: #666666;">
Iniciar sesion
</h1>
</header>
<form class="d-grid gap-3" novalidate aria-labelledby="login-title">
<div class="d-grid gap-3">
<label class="visually-hidden" for="login-email">Email</label>
<app-input id="login-email" type="email" placeholder="Email" />
<div class="d-grid gap-1">
<label class="visually-hidden" for="login-password">Contrasena</label>
<app-input id="login-password" type="password" placeholder="Contrasena" />
<div class="text-end">
<button
type="button"
class="btn btn-link p-0 border-0 text-decoration-none"
style="font-size: 12px; font-weight: 325; color: #A0A0A0;"
>
Olvide mi contrasena
</button>
</div>
</div>
</div>
<div class="d-grid gap-2 text-center">
<app-button hostClass="w-100 d-block" buttonClass="w-100">Ingresar</app-button>
<button
type="button"
class="btn btn-link border-0 text-decoration-none fw-bold px-3 py-2"
style="min-height: 40px;"
>
Crear cuenta
</button>
</div>
</form>
<div class="d-flex align-items-center justify-content-center gap-3 my-4" aria-hidden="true">
<span class="flex-grow-1" style="max-width: 18rem; height: 1px; background-color: #d9d9d9;"></span>
<span
class="rounded-circle border flex-shrink-0"
style="width: 16px; height: 16px; border-color: #d9d9d9 !important; background-color: #ffffff;"
></span>
<span class="flex-grow-1" style="max-width: 18rem; height: 1px; background-color: #d9d9d9;"></span>
</div>
<app-button
variant="neutral-outline"
hostClass="w-100 d-block"
buttonClass="w-100 d-inline-flex align-items-center justify-content-center gap-2 py-2 fw-semibold"
>
<i class="fa-brands fa-google text-danger" aria-hidden="true"></i>
<span>Continuar con Google</span>
</app-button>
</div>
</article>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ButtonComponent } from '../../../../shared/components/button/button.component';
import { InputComponent } from '../../../../shared/components/input/input.component';
@Component({
selector: 'app-login-page',
imports: [InputComponent, ButtonComponent],
templateUrl: './login-page.component.html',
styleUrl: './login-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LoginPageComponent {}

View File

@@ -1,6 +1,7 @@
import { Routes } from '@angular/router';
import { StoreLayoutComponent } from '../../core/layout/store-layout/store-layout.component';
import { LoginPageComponent } from './pages/login-page/login-page.component';
import { StoreHomePageComponent } from './pages/store-home-page/store-home-page.component';
export const routes: Routes = [
@@ -12,6 +13,10 @@ export const routes: Routes = [
path: '',
component: StoreHomePageComponent
},
{
path: 'login',
component: LoginPageComponent
},
{
path: 'producto/:id',
loadComponent: () =>

View File

@@ -29,6 +29,7 @@
}
.btn-outline-primary,
.btn-outline-neutral,
.btn-outline-danger {
--bs-btn-bg: transparent;
--bs-btn-hover-color: #fff;
@@ -85,6 +86,17 @@
--bs-btn-disabled-border-color: color-mix(in srgb, transparent 35%, var(--tenant-primary));
}
.btn-outline-neutral {
--bs-btn-color: #666666;
--bs-btn-border-color: #a0a0a0;
--bs-btn-hover-bg: #a0a0a0;
--bs-btn-hover-border-color: #a0a0a0;
--bs-btn-active-bg: #8a8a8a;
--bs-btn-active-border-color: #8a8a8a;
--bs-btn-disabled-color: rgba(102, 102, 102, 0.55);
--bs-btn-disabled-border-color: rgba(160, 160, 160, 0.55);
}
.btn-outline-danger {
--bs-btn-color: var(--tenant-danger);
--bs-btn-border-color: var(--tenant-danger);

View File

@@ -4,6 +4,7 @@ import { NgClass } from '@angular/common';
export type ButtonVariant =
| 'primary'
| 'secondary'
| 'neutral-outline'
| 'danger'
| 'danger-secondary'
| 'cancel';
@@ -13,22 +14,28 @@ export type ButtonVariant =
imports: [NgClass],
templateUrl: './button.component.html',
styleUrl: './button.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[class]': 'hostClass()'
}
})
export class ButtonComponent {
readonly variant = input<ButtonVariant>('primary');
readonly type = input<'button' | 'submit' | 'reset'>('button');
readonly disabled = input(false);
readonly hostClass = input<string>('');
readonly buttonClass = input<string>('');
protected readonly buttonClasses = computed(() => {
const variants: Record<ButtonVariant, string> = {
primary: 'btn-primary',
secondary: 'btn-outline-primary',
'neutral-outline': 'btn-outline-neutral',
danger: 'btn-danger',
'danger-secondary': 'btn-outline-danger',
cancel: 'btn-secondary'
};
return ['btn', variants[this.variant()]];
return ['btn', variants[this.variant()], this.buttonClass()];
});
}

View File

@@ -22,7 +22,7 @@
.form-control {
min-height: 40px;
padding: 0.625rem 1rem;
border-radius: 0.5rem;
border-radius: 5px;
border-color: #cccccc;
color: #666666;
font-size: 15px;