Compare commits

...

15 Commits

Author SHA1 Message Date
eb3a084d18 Merge branch 'fix/cart-expiration-notification' of https://gitea.quo.ar/tbianchini/shopit-front into fix/cart-expiration-notification 2026-08-26 16:19:11 -03:00
e4f9d38e82 fix(checkout): refresh expired carts 2026-08-26 16:19:08 -03:00
b0e2efbdd1 fix(checkout): show backend start errors 2026-08-26 16:18:48 -03:00
1ad7bccf16 fix(checkout): refresh expired carts 2026-08-26 16:16:46 -03:00
a1d6585b54 fix(checkout): show backend start errors 2026-08-26 16:16:32 -03:00
591f53c102 Squashed commit of the following:
commit 38d03588b1
Merge: 8e987d0 6f4aa3b
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Aug 26 10:25:23 2026 -0300

    Merge branch 'fix/cart-expiration-notification' of https://gitea.quo.ar/tbianchini/shopit-front into fix/cart-expiration-notification

commit 8e987d0ce6
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Aug 26 10:02:26 2026 -0300

    fix(purchase-status): clear cart on approved purchase

commit e171b9a233
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 17:06:07 2026 -0300

    refactor(checkout): use purchase id route segment

commit 643d43adea
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 17:05:59 2026 -0300

    fix(checkout): handle expired purchase exits

commit d89d01b511
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 16:47:17 2026 -0300

    fix(checkout): refresh expired carts

commit eeb245209b
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 16:46:12 2026 -0300

    fix(checkout): show backend start errors

commit 9d3754c2d8
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 16:33:44 2026 -0300

    fix(cart): refresh state after expiration

commit 6f4aa3b1bd
Author: ncoronel <ncoronel@quo.ar>
Date:   Wed Aug 26 10:02:26 2026 -0300

    fix(purchase-status): clear cart on approved purchase

commit 2b342ec235
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 17:06:07 2026 -0300

    refactor(checkout): use purchase id route segment

commit d06b146104
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 17:05:59 2026 -0300

    fix(checkout): handle expired purchase exits

commit f4e0a9e028
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 16:47:17 2026 -0300

    fix(checkout): refresh expired carts

commit 8c2b738806
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 16:46:12 2026 -0300

    fix(checkout): show backend start errors

commit 1362d0c163
Author: ncoronel <ncoronel@quo.ar>
Date:   Tue Aug 25 16:33:44 2026 -0300

    fix(cart): refresh state after expiration
2026-08-26 10:29:23 -03:00
38d03588b1 Merge branch 'fix/cart-expiration-notification' of https://gitea.quo.ar/tbianchini/shopit-front into fix/cart-expiration-notification 2026-08-26 10:25:23 -03:00
8e987d0ce6 fix(purchase-status): clear cart on approved purchase 2026-08-26 10:25:16 -03:00
e171b9a233 refactor(checkout): use purchase id route segment 2026-08-26 10:25:16 -03:00
643d43adea fix(checkout): handle expired purchase exits 2026-08-26 10:25:16 -03:00
d89d01b511 fix(checkout): refresh expired carts 2026-08-26 10:25:16 -03:00
eeb245209b fix(checkout): show backend start errors 2026-08-26 10:25:16 -03:00
9d3754c2d8 fix(cart): refresh state after expiration 2026-08-26 10:25:16 -03:00
85529f40f2 fix(register-page): implement password visibility toggle for registration fields 2026-08-26 10:02:56 -03:00
6f4aa3b1bd fix(purchase-status): clear cart on approved purchase 2026-08-26 10:02:26 -03:00
6 changed files with 76 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ import {
CheckoutService,
PurchaseDetailResponse,
} from '../../../../core/services/checkout.service';
import { CartService } from '../../../../core/services/cart/cart.service';
import { Tenant } from '../../../../core/services/tenant.interface';
import { TenantService } from '../../../../core/services/tenant.service';
import { PurchaseStatusPageComponent } from './purchase-status-page.component';
@@ -77,11 +78,13 @@ describe('PurchaseStatusPageComponent', () => {
navigate: vi.fn().mockResolvedValue(true),
navigateByUrl: vi.fn().mockResolvedValue(true),
};
const cartService = { clearCart: vi.fn() };
await TestBed.configureTestingModule({
imports: [PurchaseStatusPageComponent],
providers: [
{ provide: CheckoutService, useValue: checkoutService },
{ provide: CartService, useValue: cartService },
{ provide: TenantService, useValue: { tenant: () => tenant } },
{
provide: ActivatedRoute,
@@ -107,15 +110,22 @@ describe('PurchaseStatusPageComponent', () => {
fixture,
element: fixture.nativeElement as HTMLElement,
checkoutService,
cartService,
router,
};
}
it('clears the cart when the purchase is approved', async () => {
const { cartService } = await render(true);
expect(cartService.clearCart).toHaveBeenCalledOnce();
});
it('shows the tickets action when this purchase generated tickets', async () => {
const { element, checkoutService, router } = await render(true);
expect(checkoutService.getPurchase).toHaveBeenCalledOnce();
expect(element.textContent).toContain('Ver mis tickets');
expect(element.textContent).toContain('Mis tickets');
expect(element.textContent).not.toContain('WhatsApp');
element.querySelector<HTMLButtonElement>('app-button button')?.click();
@@ -162,11 +172,13 @@ describe('PurchaseStatusPageComponent', () => {
return this;
},
};
const cartService = { clearCart: vi.fn() };
await TestBed.configureTestingModule({
imports: [PurchaseStatusPageComponent],
providers: [
{ provide: CheckoutService, useValue: checkoutService },
{ provide: CartService, useValue: cartService },
{ provide: TenantService, useValue: { tenant: () => tenant } },
{
provide: ActivatedRoute,
@@ -189,6 +201,7 @@ describe('PurchaseStatusPageComponent', () => {
expect(checkoutService.getPurchase).toHaveBeenCalledTimes(2);
expect(fixture.nativeElement.textContent).toContain('COMPRA REALIZADA!');
expect(cartService.clearCart).toHaveBeenCalledOnce();
await vi.advanceTimersByTimeAsync(10_000);
expect(checkoutService.getPurchase).toHaveBeenCalledTimes(2);
@@ -212,6 +225,7 @@ describe('PurchaseStatusPageComponent', () => {
imports: [PurchaseStatusPageComponent],
providers: [
{ provide: CheckoutService, useValue: checkoutService },
{ provide: CartService, useValue: { clearCart: vi.fn() } },
{ provide: TenantService, useValue: { tenant: () => tenant } },
{
provide: ActivatedRoute,

View File

@@ -15,6 +15,7 @@ import {
CheckoutService,
PurchaseStatusResponse,
} from '../../../../core/services/checkout.service';
import { CartService } from '../../../../core/services/cart/cart.service';
import { findMenu } from '../../../../core/services/menu.utils';
import { TenantService } from '../../../../core/services/tenant.service';
import { ButtonComponent } from '../../../../shared/components/button/button.component';
@@ -35,6 +36,7 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly checkoutService = inject(CheckoutService);
private readonly cartService = inject(CartService);
private readonly tenantService = inject(TenantService);
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
@@ -104,6 +106,10 @@ export class PurchaseStatusPageComponent implements OnInit, OnDestroy {
this.status.set(status);
this.hasGeneratedTickets.set(purchase.has_generated_tickets === true);
if (status === 'approved') {
this.cartService.clearCart();
}
if (status === 'pending') {
this.schedulePolling();
} else {

View File

@@ -38,11 +38,13 @@
<label class="visually-hidden" for="register-password">Contraseña</label>
<app-input
id="register-password"
type="password"
type="password-toggle"
placeholder="Contraseña"
[value]="form.controls.password.value"
[invalid]="showControlError('password')"
[visible]="passwordVisible()"
(valueChange)="updateField('password', $event)"
(visibleChange)="setPasswordVisibility($event)"
/>
@if (getControlError('password'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>
@@ -51,11 +53,13 @@
<label class="visually-hidden" for="register-password-repeat">Repetir Contraseña</label>
<app-input
id="register-password-repeat"
type="password"
type="password-toggle"
placeholder="Repetir Contraseña"
[value]="form.controls.password_confirmation.value"
[invalid]="showControlError('password_confirmation')"
[visible]="passwordVisible()"
(valueChange)="updateField('password_confirmation', $event)"
(visibleChange)="setPasswordVisibility($event)"
/>
@if (getControlError('password_confirmation'); as errorMessage) {
<small class="text-danger">{{ errorMessage }}</small>

View File

@@ -12,6 +12,48 @@ describe('RegisterPageComponent', () => {
TestBed.resetTestingModule();
});
it('shows and hides both password fields with either visibility control', async () => {
await TestBed.configureTestingModule({
imports: [RegisterPageComponent],
providers: [
provideRouter([]),
{ provide: AuthService, useValue: { register: vi.fn() } },
{ provide: ModalService, useValue: { openSimple: vi.fn() } },
{ provide: ToastService, useValue: { danger: vi.fn() } }
]
}).compileComponents();
const fixture = TestBed.createComponent(RegisterPageComponent);
fixture.detectChanges();
const getPasswordInputs = () =>
Array.from(
fixture.nativeElement.querySelectorAll(
'input#register-password, input#register-password-repeat'
)
) as HTMLInputElement[];
const getVisibilityButtons = () =>
Array.from(
fixture.nativeElement.querySelectorAll('button[aria-label]')
) as HTMLButtonElement[];
expect(getPasswordInputs().map((input) => input.type)).toEqual(['password', 'password']);
getVisibilityButtons()[0].click();
fixture.detectChanges();
expect(getPasswordInputs().map((input) => input.type)).toEqual(['text', 'text']);
expect(getVisibilityButtons().map((button) => button.getAttribute('aria-label'))).toEqual([
'Ocultar contraseña',
'Ocultar contraseña'
]);
getVisibilityButtons()[1].click();
fixture.detectChanges();
expect(getPasswordInputs().map((input) => input.type)).toEqual(['password', 'password']);
});
it('submits registration data and redirects to /login on success', async () => {
const authService = {
register: vi.fn().mockReturnValue(

View File

@@ -48,6 +48,7 @@ export class RegisterPageComponent {
private readonly submittedState = signal(false);
private readonly serverErrorState = signal<string | null>(null);
private readonly isSubmittingState = signal(false);
private readonly passwordVisibleState = signal(false);
protected readonly form = this.formBuilder.nonNullable.group({
nombre_apellido: ['', [Validators.required, Validators.maxLength(TEXT_MAX_LENGTH)]],
@@ -67,6 +68,11 @@ export class RegisterPageComponent {
protected readonly submitted = this.submittedState.asReadonly();
protected readonly serverError = this.serverErrorState.asReadonly();
protected readonly isSubmitting = this.isSubmittingState.asReadonly();
protected readonly passwordVisible = this.passwordVisibleState.asReadonly();
protected setPasswordVisibility(visible: boolean): void {
this.passwordVisibleState.set(visible);
}
goToLogin(): void {
void this.router.navigate(['/login']);

View File

@@ -1,5 +1,5 @@
export const environment = {
production: false,
production: true,
nombre:"Homologación - activo",
url:"https://backend.qa.shopit.com.ar/api/",
urlDescarga:"url/"