Compare commits
12 Commits
feature/ca
...
5365811a50
| Author | SHA1 | Date | |
|---|---|---|---|
| 5365811a50 | |||
| 3c98600193 | |||
| ff3c3233c1 | |||
| 20e8578440 | |||
| 193ba7472b | |||
| bc9390394a | |||
| 4393280fd9 | |||
| 956411254e | |||
| 4cbf87feb1 | |||
| 93d49fc7e6 | |||
| 85465ce7e1 | |||
| ee83e7ab32 |
13
src/app/core/layout/auth-layout/auth-layout.component.html
Normal file
13
src/app/core/layout/auth-layout/auth-layout.component.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<section class="auth-layout">
|
||||
<div class="auth-layout__container container-xl">
|
||||
<div class="auth-layout__row row justify-content-center">
|
||||
<div class="auth-layout__column col-12 col-md-9 col-lg-7 col-xl-5">
|
||||
<article class="auth-layout__card card border-0 shadow-lg">
|
||||
<div class="auth-layout__body card-body">
|
||||
<router-outlet />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
50
src/app/core/layout/auth-layout/auth-layout.component.scss
Normal file
50
src/app/core/layout/auth-layout/auth-layout.component.scss
Normal file
@@ -0,0 +1,50 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.auth-layout {
|
||||
padding: 1.5rem 0 2rem;
|
||||
}
|
||||
|
||||
.auth-layout__container {
|
||||
padding-inline: 1rem;
|
||||
}
|
||||
|
||||
.auth-layout__row {
|
||||
padding-block: 1rem;
|
||||
}
|
||||
|
||||
.auth-layout__card {
|
||||
border-radius: 1.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auth-layout__body {
|
||||
padding: 2rem 1.5rem;
|
||||
}
|
||||
|
||||
:host ::ng-deep .auth-title {
|
||||
margin: 0;
|
||||
color: #666666;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.auth-layout {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.auth-layout__container {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
|
||||
.auth-layout__row {
|
||||
padding-block: 1.5rem;
|
||||
}
|
||||
|
||||
.auth-layout__body {
|
||||
padding: 3rem;
|
||||
}
|
||||
}
|
||||
11
src/app/core/layout/auth-layout/auth-layout.component.ts
Normal file
11
src/app/core/layout/auth-layout/auth-layout.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-layout',
|
||||
imports: [RouterOutlet],
|
||||
templateUrl: './auth-layout.component.html',
|
||||
styleUrl: './auth-layout.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AuthLayoutComponent {}
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<header class="login-page__header text-center">
|
||||
<h1 id="login-title" class="auth-title">Iniciar sesión</h1>
|
||||
</header>
|
||||
|
||||
<form class="login-page__form 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">Contraseña</label>
|
||||
<app-input id="login-password" type="password" placeholder="Contrasena" />
|
||||
<div class="text-end">
|
||||
<button type="button" class="login-page__forgot-password btn btn-link p-0 border-0 text-decoration-none">
|
||||
Olvidé mi contraseña
|
||||
</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="login-page__create-account btn btn-link border-0 text-decoration-none px-3 py-2">
|
||||
Crear cuenta
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="login-page__divider d-flex align-items-center justify-content-center gap-3" aria-hidden="true">
|
||||
<span class="login-page__divider-line flex-grow-1"></span>
|
||||
<span class="login-page__divider-dot rounded-circle border flex-shrink-0"></span>
|
||||
<span class="login-page__divider-line flex-grow-1"></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"
|
||||
[disabled]="true"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 48 48"
|
||||
width="18"
|
||||
height="18"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="#FFC107"
|
||||
d="M43.61 20.08H42V20H24v8h11.3C33.65 32.66 29.19 36 24 36c-6.63 0-12-5.37-12-12s5.37-12 12-12c3.06 0 5.84 1.15 7.96 3.04l5.66-5.66C34.09 6.05 29.3 4 24 4C12.95 4 4 12.95 4 24s8.95 20 20 20s20-8.95 20-20c0-1.34-.14-2.65-.39-3.92z"
|
||||
/>
|
||||
<path
|
||||
fill="#FF3D00"
|
||||
d="M6.31 14.69l6.57 4.82C14.66 15.1 18.96 12 24 12c3.06 0 5.84 1.15 7.96 3.04l5.66-5.66C34.09 6.05 29.3 4 24 4C16.32 4 9.66 8.34 6.31 14.69z"
|
||||
/>
|
||||
<path
|
||||
fill="#4CAF50"
|
||||
d="M24 44c5.2 0 9.9-1.99 13.47-5.23l-6.19-5.24C29.2 35.11 26.72 36 24 36c-5.17 0-9.62-3.32-11.28-7.95l-6.52 5.02C9.51 39.56 16.24 44 24 44z"
|
||||
/>
|
||||
<path
|
||||
fill="#1976D2"
|
||||
d="M43.61 20.08H42V20H24v8h11.3c-.79 2.37-2.31 4.38-4.28 5.53l.01-.01l6.19 5.24C36.78 39.13 44 34 44 24c0-1.34-.14-2.65-.39-3.92z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Continuar con Google</span>
|
||||
</app-button>
|
||||
@@ -0,0 +1,39 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login-page__header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.login-page__form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-page__forgot-password {
|
||||
color: #a0a0a0;
|
||||
font-size: 12px;
|
||||
font-weight: 325;
|
||||
}
|
||||
|
||||
.login-page__create-account {
|
||||
min-height: 40px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.login-page__divider {
|
||||
margin-block: 1.5rem;
|
||||
}
|
||||
|
||||
.login-page__divider-line {
|
||||
max-width: 18rem;
|
||||
height: 1px;
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.login-page__divider-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-color: #d9d9d9 !important;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { AuthLayoutComponent } from '../../core/layout/auth-layout/auth-layout.component';
|
||||
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 +14,16 @@ export const routes: Routes = [
|
||||
path: '',
|
||||
component: StoreHomePageComponent
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
component: AuthLayoutComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: LoginPageComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'producto/:id',
|
||||
loadComponent: () =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user