Skip to content

Commit

Permalink
feat: inital implementation of reservations ngrx
Browse files Browse the repository at this point in the history
  • Loading branch information
Pablito2020 committed Nov 13, 2023
1 parent 24f792d commit fa38cde
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 232 deletions.
4 changes: 4 additions & 0 deletions app/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { mapReducer } from './state/map/map.reducer';
import { MapEffects } from './state/map/map.effects';
import { searchReducer } from './state/components/search/search.reducer';
import { SearchEffects } from './state/components/search/search.effects';
import { ReservationsEffects } from './state/reservations/reservations.effects';
import { reservationsReducer } from './state/reservations/reservations.reducer';

export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
Expand Down Expand Up @@ -59,6 +61,7 @@ export function createTranslateLoader(http: HttpClient) {
map: mapReducer,
modal: modalReducer,
searchCompletion: searchReducer,
reservations: reservationsReducer,
},
{
// This is because inmutability objects on actions (and google maps libraries and capacitor change the object from the action)
Expand All @@ -83,6 +86,7 @@ export function createTranslateLoader(http: HttpClient) {
LanguageEffects,
MapEffects,
SearchEffects,
ReservationsEffects,
]),
],
providers: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { Component, Input, OnInit } from '@angular/core';
import { fromISOString } from '../../schemas/night/night';
import {
CreateReservation,
CreateReservationError,
} from '../../schemas/reservations/create-reservation';
import { match } from 'ts-pattern';
import { Reservation } from '../../schemas/reservations/reservation';
import { Color } from '@ionic/core';
import { AuthService } from '../../services/auth/auth.service';
import { ReservationsService } from '../../services/reservations/reservations.service';
import { Refuge } from '../../schemas/refuge/refuge';
import { ToastController } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core';
import { AppState } from 'src/app/state/app.state';
import { Store } from '@ngrx/store';
import { addReservation } from '../../state/reservations/reservations.actions';
import { getCreateReservationErrors } from '../../state/reservations/reservations.selectors';

@Component({
selector: 'app-reservation-picker',
Expand All @@ -22,111 +18,27 @@ export class ReservationPickerComponent implements OnInit {
@Input({ required: true }) refuge!: Refuge;
date = '';

constructor(
private authService: AuthService,
private toastController: ToastController,
private translateService: TranslateService,
private reservationService: ReservationsService,
) {}
createErrors = this.store.select(getCreateReservationErrors);

constructor(private store: Store<AppState>) {
this.createErrors.subscribe((errors) => {
console.log(errors);
});
}

ngOnInit() {}

onBookClick() {
const night = fromISOString(this.date);
this.authService.getUserId().then((userId) => {
if (userId === null) return;
if (this.refuge === undefined) return;
this.reservationService
.createReservation(userId, this.refuge.id, night)
.subscribe({
next: (response) => {
this.handleCreateReservationResponse(response);
},
error: () => {
this.handleClientErrorOnCreateReservation();
},
});
});
const reservation = {
refuge_id: this.refuge.id,
night,
};
this.store.dispatch(addReservation({ reservation }));
}

getCurrentDate(): string {
const date = new Date();
return date.toISOString();
}

private handleCreateReservationResponse(response: CreateReservation) {
match(response)
.with({ status: 'ok' }, (response) => {
this.handleCorrectCreateReservation(response.reservation);
})
.with({ status: 'error' }, (response) => {
this.handleCreateReservationError(response.error);
})
.exhaustive();
}

private handleCorrectCreateReservation(reservation: Reservation) {
this.showToast(
'RESERVATIONS.CREATE_OPERATION.CORRECT',
{ day: reservation.night.day },
'success',
);
}

private handleCreateReservationError(error: CreateReservationError) {
match(error)
.with(CreateReservationError.NOT_AUTHENTICATED_OR_INVALID_DATE, () => {
this.showToast(
'RESERVATIONS.CREATE_OPERATION.NOT_AUTHENTICATED_OR_INVALID_DATE',
{},
'danger',
);
})
.with(CreateReservationError.NOT_ALLOWED_CREATION_FOR_USER, () => {
this.showToast(
'RESERVATIONS.CREATE_OPERATION.NOT_ALLOWED_CREATION_FOR_USER',
{},
'danger',
);
})
.with(CreateReservationError.PROGRAMMING_ERROR, () => {
this.showToast(
'RESERVATIONS.CREATE_OPERATION.PROGRAMMING_ERROR',
{},
'danger',
);
})
.with(CreateReservationError.REFUGE_OR_USER_NOT_FOUND, () => {
this.showToast(
'RESERVATIONS.CREATE_OPERATION.REFUGE_OR_USER_NOT_FOUND',
{},
'danger',
);
})
.with(
CreateReservationError.SERVER_ERROR,
CreateReservationError.UNKNOWN_ERROR,
CreateReservationError.NTP_SERVER_IS_DOWN,
() => {
this.showToast(
'RESERVATIONS.CREATE_OPERATION.SERVER_ERROR',
{},
'danger',
);
},
)
.exhaustive();
}

private showToast(messageKey: string, props: any, color: Color) {
this.toastController
.create({
message: this.translateService.instant(messageKey, props),
duration: 2000,
color: color,
})
.then((toast) => toast.present());
}

private handleClientErrorOnCreateReservation() {}
}
10 changes: 5 additions & 5 deletions app/src/app/pages/reservations/reservations.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true" *ngIf="reservations !== undefined">
<ion-content [fullscreen]="true">
<ion-list style="padding-block: 0px">
<ion-item-group *ngFor="let res of reservations | async">
<ion-item-divider sticky>
Expand All @@ -16,13 +16,13 @@
<ion-item *ngFor="let reservation of res.reservations">
<ion-label routerLink="/home/{{reservation.refuge_id}}">
<h3>
{{'RESERVATIONS.DETAILS.HEADER' | translate: {day:
reservation.night.day} }}
{{'RESERVATIONS.DETAILS.HEADER' | translate: { day:
reservation.night.day } }}
</h3>
<p>
{{'RESERVATIONS.DETAILS.MESSAGE' | translate: {month:
{{'RESERVATIONS.DETAILS.MESSAGE' | translate: { month:
reservation.night.month, year: reservation.night.year, day:
reservation.night.day} }}
reservation.night.day } }}
</p>
</ion-label>
<ion-button
Expand Down
124 changes: 9 additions & 115 deletions app/src/app/pages/reservations/reservations.page.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { ReservationWithId } from '../../schemas/reservations/reservation';
import { UserReservationService } from '../../services/reservations/user-reservation.service';
import { AuthService } from '../../services/auth/auth.service';
import { RefugeReservationsRelations } from '../../services/reservations/grouped-by/refuge';
import { ReservationsService } from '../../services/reservations/reservations.service';
import { AlertController, ToastController } from '@ionic/angular';
import {
DeleteReservation,
DeleteReservationError,
ErrorDeleteReservation,
} from '../../schemas/reservations/delete-reservation';
import { match } from 'ts-pattern';
import { Router } from '@angular/router';
import { AlertController } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core';
import { Store } from '@ngrx/store';
import { AppState } from '../../state/app.state';
import { deleteReservation } from '../../state/reservations/reservations.actions';
import { getReservationsSortedByRefuge } from '../../state/reservations/reservations.selectors';

@Component({
selector: 'app-reservations',
templateUrl: './reservations.page.html',
styleUrls: ['./reservations.page.scss'],
})
export class ReservationsPage implements OnInit {
reservations?: Observable<RefugeReservationsRelations>;
reservations = this.store.select(getReservationsSortedByRefuge);

onRemoveReservation(reservation: ReservationWithId) {
this.showDeleteReservationMessage(reservation, () => {
Expand All @@ -30,71 +22,13 @@ export class ReservationsPage implements OnInit {
}

constructor(
userReservationService: UserReservationService,
private reservationService: ReservationsService,
private authService: AuthService,
private router: Router,
private store: Store<AppState>,
private alertController: AlertController,
private toastController: ToastController,
private translateService: TranslateService,
) {
this.authService.getUserId().then((userId) => {
if (userId == null) {
console.log('TODO: handle user not logged in');
return;
}
this.reservations =
userReservationService.getReservationsGroupedByRefugeForUser(userId);
});
}
) {}

private removeReservation(reservation: ReservationWithId) {
this.reservationService.deleteReservation(reservation.id).subscribe({
next: (response) => this.handleDeleteResponse(response),
error: () => this.handleConnectionOrServerDown().then(),
});
}

private handleDeleteResponse(response: DeleteReservation) {
match(response)
.with({ status: 'ok' }, () => this.showReservationDeletedMessage())
.with({ status: 'error' }, (error) => this.handleError(error))
.exhaustive();
}

private handleError(response: ErrorDeleteReservation) {
match(response.error)
.with(DeleteReservationError.RESERVATION_NOT_FOUND, () =>
this.handleReservationNotFound(),
)
.with(
DeleteReservationError.NOT_AUTHENTICATED,
DeleteReservationError.PROGRAMMING_ERROR,
DeleteReservationError.SERVER_ERROR,
DeleteReservationError.NOT_ALLOWED_DELETION_FOR_USER,
() => this.handleProgrammingErrors(),
)
.with(DeleteReservationError.UNKNOWN_ERROR, () =>
this.handleUnknownError(),
)
.exhaustive();
}

private async handleConnectionOrServerDown() {
const alert = await this.alertController.create({
header: this.translateService.instant('HOME.CLIENT_ERROR.HEADER'),
subHeader: this.translateService.instant('HOME.CLIENT_ERROR.SUBHEADER'),
message: this.translateService.instant('HOME.CLIENT_ERROR.MESSAGE'),
buttons: [
{
text: this.translateService.instant('HOME.CLIENT_ERROR.OKAY_BUTTON'),
handler: () => {
this.alertController.dismiss().then();
},
},
],
});
return await alert.present();
this.store.dispatch(deleteReservation({ id: reservation.id }));
}

private async showDeleteReservationMessage(
Expand Down Expand Up @@ -125,45 +59,5 @@ export class ReservationsPage implements OnInit {
return await alert.present();
}

private showReservationDeletedMessage() {
this.toastController
.create({
message: this.translateService.instant(
'RESERVATIONS.DELETE_OPERATION.CORRECT',
),
duration: 2000,
color: 'success',
})
.then((toast) => toast.present());
}

private handleReservationNotFound() {
this.toastController
.create({
message: this.translateService.instant(
'RESERVATIONS.DELETE_OPERATION.NOT_FOUND',
),
duration: 2000,
color: 'danger',
})
.then((toast) => toast.present());
}

ngOnInit() {}

private handleProgrammingErrors() {
this.router
.navigate(['programming-error'], {
skipLocationChange: true,
})
.then();
}

private handleUnknownError() {
this.router
.navigate(['internal-error-page'], {
skipLocationChange: true,
})
.then();
}
}
8 changes: 6 additions & 2 deletions app/src/app/schemas/reservations/create-reservation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { isMatching, match } from 'ts-pattern';
import { Reservation, ReservationPattern } from './reservation';
import {
Reservation,
ReservationPattern,
ReservationWithId,
} from './reservation';

export enum CreateReservationError {
REFUGE_OR_USER_NOT_FOUND = 'REFUGE_OR_USER_NOT_FOUND',
Expand Down Expand Up @@ -46,7 +50,7 @@ export namespace CreateReservationError {

export type CorrectCreateReservation = {
status: 'ok';
reservation: Reservation;
reservation: ReservationWithId;
};

export type ErrorCreateReservation = {
Expand Down
8 changes: 6 additions & 2 deletions app/src/app/schemas/reservations/delete-reservation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { isMatching, match } from 'ts-pattern';
import { Reservation, ReservationPattern } from './reservation';
import {
Reservation,
ReservationPattern,
ReservationWithId,
} from './reservation';

export enum DeleteReservationError {
RESERVATION_NOT_FOUND = 'RESERVATION_NOT_FOUND',
Expand Down Expand Up @@ -41,7 +45,7 @@ export namespace DeleteReservationError {

export type CorrectDeleteReservation = {
status: 'ok';
reservation: Reservation;
reservation: ReservationWithId;
};

export type ErrorDeleteReservation = {
Expand Down
Loading

0 comments on commit fa38cde

Please sign in to comment.