From 0a59f5e5ba60de7e91138e0a0a3d742060b626bc Mon Sep 17 00:00:00 2001 From: Nicolas Fedor Date: Mon, 1 Apr 2024 12:34:27 +0100 Subject: [PATCH] Validate DTO before form is closed, display errors if any --- ...eate-or-update-account-dialog.component.ts | 7 +++ .../create-or-update-account-dialog.html | 28 +++++++++ ...ate-or-update-activity-dialog.component.ts | 14 +++-- .../create-or-update-activity-dialog.html | 63 +++++++++++++++++++ apps/client/src/app/util/validation.util.ts | 27 ++++++++ 5 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/app/util/validation.util.ts diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index c97bbf113..0aa3c3093 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -1,6 +1,7 @@ import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { validateObjectForForm } from '@ghostfolio/client/util/validation.util'; import { Currency } from '@ghostfolio/common/interfaces'; import { @@ -115,8 +116,14 @@ export class CreateOrUpdateAccountDialog implements OnDestroy { if (this.data.account.id) { (account as UpdateAccountDto).id = this.data.account.id; + validateObjectForForm(account, UpdateAccountDto, this.accountForm, () => { + this.dialogRef.close({ account }); + }); } else { delete (account as CreateAccountDto).id; + validateObjectForForm(account, CreateAccountDto, this.accountForm, () => { + this.dialogRef.close({ account }); + }); } this.dialogRef.close({ account }); diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html index e2981462f..007835a21 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -18,6 +18,9 @@ matInput (keydown.enter)="$event.stopPropagation()" /> + + {{ accountForm.controls['name'].errors?.validationError }} +
@@ -27,6 +30,11 @@ formControlName="currency" [currencies]="currencies" /> + + {{ accountForm.controls['currency'].errors?.validationError }} +
@@ -41,6 +49,11 @@ {{ accountForm.controls['currency']?.value?.value }} + + {{ accountForm.controls['balance'].errors?.validationError }} +
@@ -71,6 +84,11 @@ } + + {{ accountForm.controls['platformId'].errors?.validationError }} +
@@ -83,6 +101,11 @@ matInput (keyup.enter)="$event.stopPropagation()" > + + {{ accountForm.controls['comment'].errors?.validationError }} +
@@ -95,6 +118,11 @@ Account ID + + {{ accountForm.controls['accountId'].errors?.validationError }} +
} diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 21a2ca920..ac966d221 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -1,6 +1,7 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { validateObjectForForm } from '@ghostfolio/client/util/validation.util'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { translate } from '@ghostfolio/ui/i18n'; @@ -19,7 +20,8 @@ import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { AssetClass, AssetSubClass, Tag, Type } from '@prisma/client'; -import { isUUID } from 'class-validator'; +import { plainToInstance } from 'class-transformer'; +import { Validator, isUUID, validate } from 'class-validator'; import { isToday } from 'date-fns'; import { EMPTY, Observable, Subject, lastValueFrom, of } from 'rxjs'; import { catchError, delay, map, startWith, takeUntil } from 'rxjs/operators'; @@ -459,7 +461,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { comment: this.activityForm.controls['comment'].value, currency: this.activityForm.controls['currency'].value, customCurrency: this.activityForm.controls['currencyOfUnitPrice'].value, - date: this.activityForm.controls['date'].value, + date: (this.activityForm.controls['date'].value as Date).toISOString(), dataSource: this.activityForm.controls['dataSource'].value, fee: this.activityForm.controls['fee'].value, quantity: this.activityForm.controls['quantity'].value, @@ -476,12 +478,16 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { if (this.data.activity.id) { (activity as UpdateOrderDto).id = this.data.activity.id; + validateObjectForForm(activity, UpdateOrderDto, this.activityForm, () => { + this.dialogRef.close({ activity }); + }); } else { (activity as CreateOrderDto).updateAccountBalance = this.activityForm.controls['updateAccountBalance'].value; + validateObjectForForm(activity, CreateOrderDto, this.activityForm, () => { + this.dialogRef.close({ activity }); + }); } - - this.dialogRef.close({ activity }); } public ngOnDestroy() { diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 79ea7647a..37391a482 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -71,6 +71,11 @@ > + + {{ activityForm.controls['tye'].errors?.validationError }} +
@@ -103,6 +108,11 @@
+ + {{ activityForm.controls['accountId'].errors?.validationError }} +
@@ -124,6 +134,11 @@ formControlName="searchSymbol" [isLoading]="isLoading" /> + + {{ activityForm.controls['searchSymbol'].errors?.validationError }} +
Name + + {{ activityForm.controls['name'].errors?.validationError }} +
@@ -147,12 +167,22 @@ currency }} + + {{ activityForm.controls['currency'].errors?.validationError }} +
Data Source + + {{ activityForm.controls['dataSource'].errors?.validationError }} +
@@ -167,6 +197,11 @@ /> + + {{ activityForm.controls['date'].errors?.validationError }} +
Quantity + + {{ activityForm.controls['quantity'].errors?.validationError }} +
{{ activityForm.controls['currency'].value }} + + {{ activityForm.controls['unitPrice'].errors?.validationError }} +
{{ activityForm.controls['currency'].value }} + + {{ activityForm.controls['fee'].errors?.validationError }} +
@@ -324,6 +372,11 @@ matInput (keyup.enter)="$event.stopPropagation()" > + + {{ activityForm.controls['comment'].errors?.validationError }} +
{{ assetClass.label }} + + {{ activityForm.controls['assetClass'].errors?.validationError }} +
{{ assetSubClass.label }} + + {{ activityForm.controls['assetSubClass'].errors?.validationError }} +
diff --git a/apps/client/src/app/util/validation.util.ts b/apps/client/src/app/util/validation.util.ts new file mode 100644 index 000000000..42172345a --- /dev/null +++ b/apps/client/src/app/util/validation.util.ts @@ -0,0 +1,27 @@ +import { FormGroup } from '@angular/forms'; +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; + +export function validateObjectForForm( + obj: T, + ctor: { new (): T }, + form: FormGroup, + onSuccess: () => void +): void { + const objInstance = plainToInstance(ctor, obj); + + validate(objInstance as object).then((errors) => { + if (errors.length === 0) { + onSuccess(); + } else { + errors.forEach((error) => { + const formControl = form.get(error.property); + if (formControl) { + formControl.setErrors({ + validationError: Object.values(error.constraints)[0] + }); + } + }); + } + }); +}