Skip to content

Commit

Permalink
display spinner on submit + refresh category (#1388)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbellone authored Aug 22, 2024
1 parent ece983c commit 7dd2d18
Show file tree
Hide file tree
Showing 24 changed files with 218 additions and 82 deletions.
3 changes: 2 additions & 1 deletion frontend/projects/public/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
faAngleUp,
faCheck,
faCircle,
faCircleNotch,
faCog,
faCreditCard,
faDownload,
Expand Down Expand Up @@ -251,7 +252,7 @@ export class AppModule {
library.addIcons(faInfoCircle, faGift, faTicketAlt, faCheck, faAddressCard, faFileAlt, faThumbsUp, faMoneyBill,
faDownload, faSearchPlus, faExchangeAlt, faExclamationTriangle, faCreditCard, faCog, faEraser, faTimes, faFileInvoice, faGlobe,
faAngleDown, faAngleUp, faCircle, faCheckCircle, faMoneyCheckAlt, faWifi, faTrash, faCopy, faExclamationCircle, faUserAstronaut,
faSignInAlt, faSignOutAlt, faExternalLinkAlt, faMapMarkerAlt, faRedoAlt);
faSignInAlt, faSignOutAlt, faExternalLinkAlt, faMapMarkerAlt, faRedoAlt, faCircleNotch);
library.addIcons(faCalendarAlt, faCalendarPlus, faCompass, faClock, faEnvelope, faEdit, faClone, faHandshake, faBuilding);
library.addIcons(faPaypal, faStripe, faIdeal, faApplePay, faFilePdf);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,15 @@ <h3 translate="show-event.sold-out.header"></h3>
<app-ticket-quantity-selector [parentGroup]="reservationFormItem(reservationForm, counter)"
[category]="category"
[quantityRange]="ticketCategoryAmount[category.id]"
(valueChange)="selectionChange()"></app-ticket-quantity-selector>
(valueChange)="selectionChange()"
(refreshCommand)="handleRefreshCommand()"></app-ticket-quantity-selector>
</div>
@if (!waitingList && !category.saleableAndLimitNotReached && category.containsPendingTickets) {
<div class="item-info border-top pt-2">
<fa-icon [icon]="['fas', 'info-circle']" class="text-primary"></fa-icon>
{{ 'show-event.all-tickets-reserved.info' | translate }}
</div>
}
</app-item-card>
</div>

Expand Down Expand Up @@ -206,7 +213,14 @@ <h5 class="alert-heading"><fa-icon [icon]="event.promotionsConfiguration.usePart
<hr class="mt-5">

<div class="row d-flex justify-content-between mobile-add-margin-bottom">
<div class="col-md-5 order-md-1 col-12"><button type="submit" class="block-button btn btn-success" translate="show-event.continue"></button></div>
<div class="col-md-5 order-md-1 col-12">
<button type="submit" class="block-button btn btn-success">
@if (submitInProgress) {
<fa-icon [icon]="['fas', 'circle-notch']" [animation]="'spin'"></fa-icon>
}
<span [ngClass]="{'ms-2': submitInProgress}">{{(submitInProgress ? 'reservation.payment-in-progress' : 'show-event.continue') | translate}}</span>
</button>
</div>
<div class="col-md-5 order-md-0 col-12 "><a [href]="event.websiteUrl" class="block-button btn btn-light" translate="to-event-site"></a></div>
</div>
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {EventService} from '../shared/event.service';
import {ActivatedRoute, Router} from '@angular/router';
import {UntypedFormArray, UntypedFormBuilder, UntypedFormGroup} from '@angular/forms';
Expand All @@ -8,7 +8,7 @@ import {TranslateService} from '@ngx-translate/core';
import {TicketCategory} from '../model/ticket-category';
import {ReservationRequest} from '../model/reservation-request';
import {handleServerSideValidationError} from '../shared/validation-helper';
import {zip} from 'rxjs';
import {debounceTime, Subject, Subscription, zip} from 'rxjs';
import {AdditionalService} from '../model/additional-service';
import {I18nService} from '../shared/i18n.service';
import {WaitingListSubscriptionRequest} from '../model/waiting-list-subscription-request';
Expand All @@ -17,13 +17,14 @@ import {DynamicDiscount, EventCode} from '../model/event-code';
import {AnalyticsService} from '../shared/analytics.service';
import {ErrorDescriptor} from '../model/validated-response';
import {SearchParams} from '../model/search-params';
import {FeedbackService} from "../shared/feedback/feedback.service";

@Component({
selector: 'app-event-display',
templateUrl: './event-display.component.html',
styleUrls: ['./event-display.component.scss']
})
export class EventDisplayComponent implements OnInit {
export class EventDisplayComponent implements OnInit, OnDestroy {

event: AlfioEvent;
ticketCategories: TicketCategory[];
Expand Down Expand Up @@ -59,6 +60,9 @@ export class EventDisplayComponent implements OnInit {
expiredCategoriesExpanded = false;

private dynamicDiscount: DynamicDiscount;
private refreshDebouncer = new Subject<any>();
private subscription?: Subscription;
submitInProgress: boolean = false;

// https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/

Expand All @@ -70,10 +74,14 @@ export class EventDisplayComponent implements OnInit {
private formBuilder: UntypedFormBuilder,
public translate: TranslateService,
private i18nService: I18nService,
private analytics: AnalyticsService) { }
private analytics: AnalyticsService,
private feedbackService: FeedbackService) { }

ngOnInit(): void {

this.subscription = this.refreshDebouncer
.pipe(debounceTime(500))
.subscribe(() => this.doRefreshCategories());
const code = this.route.snapshot.queryParams['code'];
const errors = this.route.snapshot.queryParams['errors'];
if (errors) {
Expand Down Expand Up @@ -109,6 +117,10 @@ export class EventDisplayComponent implements OnInit {
});
}

ngOnDestroy() {
this.subscription?.unsubscribe();
}

private applyItemsByCat(itemsByCat: ItemsByCategory) {
this.ticketCategories = itemsByCat.ticketCategories;
this.expiredCategories = itemsByCat.expiredCategories || [];
Expand Down Expand Up @@ -150,6 +162,11 @@ export class EventDisplayComponent implements OnInit {
}

submitForm(eventShortName: string, reservation: ReservationRequest) {
if (this.submitInProgress) {
// ignoring click, as there is a pending request
return;
}
this.submitInProgress = true;
const request = reservation;
if (reservation.additionalService != null && reservation.additionalService.length > 0) {
request.additionalService = reservation.additionalService.filter(as => (as.amount != null && as.amount > 0) || (as.quantity != null && as.quantity > 0));
Expand All @@ -161,8 +178,10 @@ export class EventDisplayComponent implements OnInit {
queryParams: SearchParams.transformParams(this.route.snapshot.queryParams, this.route.snapshot.params)
});
}
this.submitInProgress = false;
},
error: err => {
this.submitInProgress = false;
this.globalErrors = handleServerSideValidationError(err, this.reservationForm);
this.scrollToTickets();
}
Expand Down Expand Up @@ -298,7 +317,18 @@ export class EventDisplayComponent implements OnInit {
}

get displayMap(): boolean {
return (this.event.mapUrl && this.event.mapUrl.length > 0) && !this.isEventOnline;
return (this.event.mapUrl?.length > 0) && !this.isEventOnline;
}

handleRefreshCommand() {
this.refreshDebouncer.next(null);
}

private doRefreshCategories() {
this.eventService.getEventTicketsInfo(this.event.shortName)
.subscribe(itemsByCat => {
this.applyItemsByCat(itemsByCat);
this.feedbackService.showSuccess('show-event.category-refresh.complete');
})
}
}
3 changes: 3 additions & 0 deletions frontend/projects/public/src/app/item-card/item-card.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ <h3 class="card-title pe-1">
<div class="card-text pt-2">
<div class="markdown-content mt-2" [innerHTML]="item.description[currentLang]"></div>
</div>
<div>
<ng-content select=".item-info"></ng-content>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions frontend/projects/public/src/app/model/ticket-category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface TicketCategory {
//

saleableAndLimitNotReached: boolean;
containsPendingTickets: boolean;
accessRestricted: boolean;
soldOutOrLimitReached: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="col-12" [class.col-md-5]="verticalLayout" *ngIf="activePaymentsCount > 1">
<div *ngIf="verticalLayout" class="mb-md-4">
<div *ngFor="let method of sortedAvailablePaymentMethods" class="form-check pt-3 pb-3"
[ngClass]="{ 'font-weight-bold border-top border-bottom': selectedPaymentMethod == method, 'text-body-secondary border-right-md': selectedPaymentMethod != null && selectedPaymentMethod != method }">
[ngClass]="{ 'fw-bold border-top border-bottom': selectedPaymentMethod == method, 'text-body-secondary border-right-md': selectedPaymentMethod != null && selectedPaymentMethod != method }">
<input class="form-check-input" [attr.id]="method" type="radio" [value]="method" name="selectedPaymentMethod" formControlName="selectedPaymentMethod" role="radiogroup" [attr.aria-labelledby]="method + '-label'">
<label class="form-check-label ms-2 w-100 h-100" [attr.for]="method" [id]="method + '-label'"><fa-icon [icon]="getPaymentMethodDetails(method).icon" a11yRole="presentation" class="ms-2 me-3"></fa-icon> {{ getPaymentMethodDetails(method).labelKey | translate }}</label>
</div>
Expand All @@ -11,7 +11,7 @@
<div class="col-12 col-md-auto mb-2 mb-md-0 me-0 me-md-3" *ngFor="let method of sortedAvailablePaymentMethods" [class.active]="selectedPaymentMethod == method">
<div class="form-check me-sm-2">
<input class="form-check-input" [attr.id]="method" type="radio" [value]="method" name="selectedPaymentMethod" formControlName="selectedPaymentMethod" [attr.aria-labelledby]="method + '-label'">
<label class="form-check-label ms-2 w-100 h-100" [attr.for]="method" [id]="method + '-label'" [ngClass]="{ 'font-weight-bold': selectedPaymentMethod == method, 'text-body-secondary': selectedPaymentMethod != null && selectedPaymentMethod != method }">
<label class="form-check-label ms-2 w-100 h-100" [attr.for]="method" [id]="method + '-label'" [ngClass]="{ 'fw-bold': selectedPaymentMethod == method, 'text-body-secondary': selectedPaymentMethod != null && selectedPaymentMethod != method }">
<fa-icon [icon]="getPaymentMethodDetails(method).icon" class="ms-2 me-3" a11yRole="presentation"></fa-icon> {{ getPaymentMethodDetails(method).labelKey | translate }}
</label>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ <h3 translate="show-subscription.sold-out.message"></h3>
<hr class="mt-5">

<div class="row d-flex justify-content-between mobile-add-margin-bottom">
<div class="col-md-5 order-md-1 col-12"><button type="submit" *ngIf="subscription.numAvailable > 0" class="block-button btn btn-success" translate="show-event.continue"></button></div>
<div class="col-md-5 order-md-1 col-12">
@if (subscription.numAvailable > 0) {
<button type="submit" class="block-button btn btn-success">
@if (submitInProgress) {
<fa-icon [icon]="['fas', 'circle-notch']" [animation]="'spin'"></fa-icon>
}
<span [ngClass]="{'ms-2': submitInProgress}">{{(submitInProgress ? 'reservation.payment-in-progress' : 'show-event.continue') | translate}}</span>
</button>
}
</div>
<div class="col-md-5 order-md-0 col-12 "><a [href]="subscription.websiteUrl" class="block-button btn btn-light" translate="common.back-to-organizer"></a></div>
</div>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class SubscriptionDisplayComponent implements OnInit {
submitError: ErrorDescriptor;
subscription: SubscriptionInfo;
subscriptionId: string;
submitInProgress: boolean = false;

constructor(private route: ActivatedRoute,
private router: Router,
Expand All @@ -45,18 +46,28 @@ export class SubscriptionDisplayComponent implements OnInit {


submitForm() {
this.subscriptionService.reserve(this.subscriptionId).subscribe(res => {
this.router.navigate(['subscription', this.subscriptionId, 'reservation', res.value, 'book']);
}, (err) => {
const errorObject = getErrorObject(err);
let errorCode: string;
if (errorObject != null) {
errorCode = errorObject.validationErrors[0].code;
} else {
errorCode = 'reservation-page-error-status.header.title';
if (this.submitInProgress) {
// ignoring click, as there is a pending request
return;
}
this.feedbackService.showError(errorCode);
});
this.submitInProgress = true;
this.subscriptionService.reserve(this.subscriptionId).subscribe({
next: res => {
this.submitInProgress = false;
this.router.navigate(['subscription', this.subscriptionId, 'reservation', res.value, 'book']);
},
error: err => {
this.submitInProgress = false;
const errorObject = getErrorObject(err);
let errorCode: string;
if (errorObject != null) {
errorCode = errorObject.validationErrors[0].code;
} else {
errorCode = 'reservation-page-error-status.header.title';
}
this.feedbackService.showError(errorCode);
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</ng-container>
</ng-container>
<dt class="col-icon text-body-secondary"><fa-icon [icon]="['fas', 'money-bill']" [title]="'reservation-page.price' | translate"></fa-icon></dt>
<dd class="col-text font-weight-bold"><app-price-tag [formattedPrice]="subscription.formattedPrice" [purchaseContext]="subscription" [showTaxDetails]="true" [singleLineLayout]="true"></app-price-tag></dd>
<dd class="col-text fw-bold"><app-price-tag [formattedPrice]="subscription.formattedPrice" [purchaseContext]="subscription" [showTaxDetails]="true" [singleLineLayout]="true"></app-price-tag></dd>

<ng-container *ngIf="hasData(owner)">
<dt class="col-icon text-body-secondary"><fa-icon [icon]="['fas', 'address-card']" [title]="'reservation-page-complete.subscription.owner' | translate"></fa-icon></dt>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ export class TicketQuantitySelectorComponent {
@Output()
valueChange = new EventEmitter<number>();

@Output()
refreshCommand = new EventEmitter<number>();

formGroup: UntypedFormGroup;

selectionChanged(): void {
this.valueChange.next(this.parentGroup.get('amount').value);
}

refreshCategories(): void {
this.refreshCommand.next(new Date().getTime());
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
<div [formGroup]="parentGroup">
<ng-container *ngIf="category.saleableAndLimitNotReached">
@if (category.saleableAndLimitNotReached) {
<label class="sr-only" [for]="'category-'+category.id+'-qty'" translate="show-event.category.quantity"></label>
<select [id]="'category-'+category.id+'-qty'" formControlName="amount" class="form-select" (change)="selectionChanged()">
<option *ngFor="let qty of quantityRange" [ngValue]="qty">{{qty}}</option>
@for (qty of quantityRange; track qty) {
<option [ngValue]="qty">{{qty}}</option>
}
</select>
</ng-container>
<span class="font-weight-bold" *ngIf="!category.saleableAndLimitNotReached && category.soldOutOrLimitReached" translate="show-event.sold-out"></span>
<span class="font-weight-bold" *ngIf="!category.saleableAndLimitNotReached && !category.soldOutOrLimitReached" translate="show-event.not-available"></span>
} @else {
<div class="d-flex flex-column align-items-center gap-2">
@if (category.containsPendingTickets) {
<span class="fw-bold" translate="show-event.all-tickets-reserved"></span>
} @else if (category.soldOutOrLimitReached) {
<span class="fw-bold" translate="show-event.sold-out"></span>
} @else {
<span class="fw-bold" translate="show-event.not-available"></span>
}

@if (category.containsPendingTickets || !category.soldOutOrLimitReached) {
<button type="button" class="btn btn-sm btn-default" (click)="refreshCategories()">
<fa-icon [icon]="['fas', 'redo-alt']"></fa-icon> {{'common.refresh' | translate}}
</button>
}
</div>
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</header>
<main class="mt-5">
<div class="alert mb-5" [ngClass]="{'alert-info': !ticket.onlineEventStarted, 'alert-success': ticket.onlineEventStarted}" *ngIf="isOnlineTicket && !ticketFormVisible">
<div class="font-weight-bold text-center">
<div class="fw-bold text-center">
<span *ngIf="!ticket.onlineEventStarted">{{'event.online.not-started' | translate:{ '0' : ticketOnlineCheckInDate } }}</span>
<span *ngIf="ticket.onlineEventStarted">{{'event.online.started' | translate }}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,17 @@
import alfio.util.ClockProvider;
import alfio.util.EventUtil;
import alfio.util.ExportUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static alfio.model.system.ConfigurationKeys.STOP_WAITING_QUEUE_SUBSCRIPTIONS;
Expand Down Expand Up @@ -69,8 +68,11 @@ private Map<String, Boolean> loadStatus(Event event) {
List<SaleableTicketCategory> stcList = eventManager.loadTicketCategories(event)
.stream()
.filter(tc -> !tc.isAccessRestricted())
.map(tc -> new SaleableTicketCategory(tc, now, event, ticketReservationManager.countAvailableTickets(event, tc), tc.getMaxTickets(), null))
.collect(Collectors.toList());
.map(tc -> {
var categoryAvailability = ticketReservationManager.countAvailableTickets(event, tc);
return new SaleableTicketCategory(tc, now, event, categoryAvailability, tc.getMaxTickets(), null);
})
.toList();
boolean active = EventUtil.checkWaitingQueuePreconditions(event, stcList, configurationManager, eventStatisticsManager.noSeatsAvailable());
boolean paused = active && configurationManager.getFor(STOP_WAITING_QUEUE_SUBSCRIPTIONS, event.getConfigurationLevel()).getValueAsBooleanOrDefault();
Map<String, Boolean> result = new HashMap<>();
Expand Down
Loading

0 comments on commit 7dd2d18

Please sign in to comment.