Browse Source

Merge remote-tracking branch 'origin/main' into feature/enable-strict-null-checks-in-ui

pull/6264/head
KenTandrian 1 week ago
parent
commit
dcc828d0ef
  1. 4
      CHANGELOG.md
  2. 35
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  3. 20
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  4. 19
      apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts
  5. 2
      libs/common/src/lib/permissions.ts

4
CHANGELOG.md

@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved the language localization for Polish (`pl`) - Improved the language localization for Polish (`pl`)
- Upgraded `@trivago/prettier-plugin-sort-imports` from version `5.2.2` to `6.0.2` - Upgraded `@trivago/prettier-plugin-sort-imports` from version `5.2.2` to `6.0.2`
### Fixed
- Fixed an issue by adding a missing guard in the public access for portfolio sharing
## 2.250.0 - 2026-03-17 ## 2.250.0 - 2026-03-17
### Added ### Added

35
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -20,9 +20,10 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Inject, DestroyRef,
OnDestroy Inject
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { import {
FormBuilder, FormBuilder,
FormGroup, FormGroup,
@ -46,8 +47,8 @@ import { AssetClass, Tag, Type } from '@prisma/client';
import { isAfter, isToday } from 'date-fns'; import { isAfter, isToday } from 'date-fns';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; import { calendarClearOutline, refreshOutline } from 'ionicons/icons';
import { EMPTY, Subject } from 'rxjs'; import { EMPTY } from 'rxjs';
import { catchError, delay, takeUntil } from 'rxjs/operators'; import { catchError, delay } from 'rxjs/operators';
import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces';
import { ActivityType } from './types/activity-type.type'; import { ActivityType } from './types/activity-type.type';
@ -75,7 +76,7 @@ import { ActivityType } from './types/activity-type.type';
styleUrls: ['./create-or-update-activity-dialog.scss'], styleUrls: ['./create-or-update-activity-dialog.scss'],
templateUrl: 'create-or-update-activity-dialog.html' templateUrl: 'create-or-update-activity-dialog.html'
}) })
export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { export class GfCreateOrUpdateActivityDialogComponent {
public activityForm: FormGroup; public activityForm: FormGroup;
public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass) public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass)
@ -101,13 +102,12 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
public typesTranslationMap = new Map<Type, string>(); public typesTranslationMap = new Map<Type, string>();
public Validators = Validators; public Validators = Validators;
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams, @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams,
private dataService: DataService, private dataService: DataService,
private dateAdapter: DateAdapter<any>, private dateAdapter: DateAdapter<any>,
private destroyRef: DestroyRef,
public dialogRef: MatDialogRef<GfCreateOrUpdateActivityDialogComponent>, public dialogRef: MatDialogRef<GfCreateOrUpdateActivityDialogComponent>,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@Inject(MAT_DATE_LOCALE) private locale: string, @Inject(MAT_DATE_LOCALE) private locale: string,
@ -133,7 +133,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.dataService this.dataService
.fetchPortfolioHoldings() .fetchPortfolioHoldings()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ holdings }) => { .subscribe(({ holdings }) => {
this.defaultLookupItems = holdings this.defaultLookupItems = holdings
.filter(({ assetSubClass }) => { .filter(({ assetSubClass }) => {
@ -237,7 +237,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
// Slightly delay until the more specific form control value changes have // Slightly delay until the more specific form control value changes have
// completed // completed
delay(300), delay(300),
takeUntil(this.unsubscribeSubject) takeUntilDestroyed(this.destroyRef)
) )
.subscribe(async () => { .subscribe(async () => {
if ( if (
@ -284,7 +284,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.activityForm this.activityForm
.get('assetClass') .get('assetClass')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject)) .valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((assetClass) => { .subscribe((assetClass) => {
const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? [];
@ -335,7 +335,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
if (newTag && this.hasPermissionToCreateOwnTag) { if (newTag && this.hasPermissionToCreateOwnTag) {
this.dataService this.dataService
.postTag({ ...newTag, userId: this.data.user.id }) .postTag({ ...newTag, userId: this.data.user.id })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((tag) => { .subscribe((tag) => {
this.activityForm.get('tags').setValue( this.activityForm.get('tags').setValue(
tags.map((currentTag) => { tags.map((currentTag) => {
@ -349,7 +349,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.userService this.userService
.get(true) .get(true)
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(); .subscribe();
}); });
} }
@ -357,7 +357,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.activityForm this.activityForm
.get('type') .get('type')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject)) .valueChanges.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((type: ActivityType) => { .subscribe((type: ActivityType) => {
if ( if (
type === 'VALUABLE' || type === 'VALUABLE' ||
@ -465,7 +465,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
dataSource: this.data.activity?.SymbolProfile?.dataSource, dataSource: this.data.activity?.SymbolProfile?.dataSource,
symbol: this.data.activity?.SymbolProfile?.symbol symbol: this.data.activity?.SymbolProfile?.symbol
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ marketPrice }) => { .subscribe(({ marketPrice }) => {
this.currentMarketPrice = marketPrice; this.currentMarketPrice = marketPrice;
@ -557,11 +557,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
} }
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private updateAssetProfile() { private updateAssetProfile() {
this.isLoading = true; this.isLoading = true;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
@ -581,7 +576,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
return EMPTY; return EMPTY;
}), }),
takeUntil(this.unsubscribeSubject) takeUntilDestroyed(this.destroyRef)
) )
.subscribe(({ currency, dataSource, marketPrice }) => { .subscribe(({ currency, dataSource, marketPrice }) => {
if (this.mode === 'create') { if (this.mode === 'create') {

20
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -21,9 +21,10 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Inject, DestroyRef,
OnDestroy Inject
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { import {
FormBuilder, FormBuilder,
FormGroup, FormGroup,
@ -52,7 +53,6 @@ import { cloudUploadOutline, warningOutline } from 'ionicons/icons';
import { isArray, sortBy } from 'lodash'; import { isArray, sortBy } from 'lodash';
import ms from 'ms'; import ms from 'ms';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, takeUntil } from 'rxjs';
import { ImportStep } from './enums/import-step'; import { ImportStep } from './enums/import-step';
import { ImportActivitiesDialogParams } from './interfaces/interfaces'; import { ImportActivitiesDialogParams } from './interfaces/interfaces';
@ -81,7 +81,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces';
styleUrls: ['./import-activities-dialog.scss'], styleUrls: ['./import-activities-dialog.scss'],
templateUrl: 'import-activities-dialog.html' templateUrl: 'import-activities-dialog.html'
}) })
export class GfImportActivitiesDialogComponent implements OnDestroy { export class GfImportActivitiesDialogComponent {
public accounts: CreateAccountWithBalancesDto[] = []; public accounts: CreateAccountWithBalancesDto[] = [];
public activities: Activity[] = []; public activities: Activity[] = [];
public assetProfileForm: FormGroup; public assetProfileForm: FormGroup;
@ -104,12 +104,11 @@ export class GfImportActivitiesDialogComponent implements OnDestroy {
public tags: CreateTagDto[] = []; public tags: CreateTagDto[] = [];
public totalItems: number; public totalItems: number;
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams, @Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams,
private dataService: DataService, private dataService: DataService,
private destroyRef: DestroyRef,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
public dialogRef: MatDialogRef<GfImportActivitiesDialogComponent>, public dialogRef: MatDialogRef<GfImportActivitiesDialogComponent>,
@ -152,7 +151,7 @@ export class GfImportActivitiesDialogComponent implements OnDestroy {
], ],
range: 'max' range: 'max'
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ holdings }) => { .subscribe(({ holdings }) => {
this.holdings = sortBy(holdings, ({ name }) => { this.holdings = sortBy(holdings, ({ name }) => {
return name.toLowerCase(); return name.toLowerCase();
@ -237,7 +236,7 @@ export class GfImportActivitiesDialogComponent implements OnDestroy {
dataSource, dataSource,
symbol symbol
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ activities }) => { .subscribe(({ activities }) => {
this.activities = activities; this.activities = activities;
this.dataSource = new MatTableDataSource(activities.reverse()); this.dataSource = new MatTableDataSource(activities.reverse());
@ -284,11 +283,6 @@ export class GfImportActivitiesDialogComponent implements OnDestroy {
}); });
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private async handleFile({ private async handleFile({
file, file,
stepper stepper

19
apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts

@ -8,10 +8,11 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
Inject, Inject,
OnDestroy,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
@ -27,8 +28,6 @@ import {
checkmarkOutline, checkmarkOutline,
copyOutline copyOutline
} from 'ionicons/icons'; } from 'ionicons/icons';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { UserAccountRegistrationDialogParams } from './interfaces/interfaces'; import { UserAccountRegistrationDialogParams } from './interfaces/interfaces';
@ -53,7 +52,7 @@ import { UserAccountRegistrationDialogParams } from './interfaces/interfaces';
styleUrls: ['./user-account-registration-dialog.scss'], styleUrls: ['./user-account-registration-dialog.scss'],
templateUrl: 'user-account-registration-dialog.html' templateUrl: 'user-account-registration-dialog.html'
}) })
export class GfUserAccountRegistrationDialogComponent implements OnDestroy { export class GfUserAccountRegistrationDialogComponent {
@ViewChild(MatStepper) stepper!: MatStepper; @ViewChild(MatStepper) stepper!: MatStepper;
public accessToken: string; public accessToken: string;
@ -64,12 +63,11 @@ export class GfUserAccountRegistrationDialogComponent implements OnDestroy {
public routerLinkAboutTermsOfService = public routerLinkAboutTermsOfService =
publicRoutes.about.subRoutes.termsOfService.routerLink; publicRoutes.about.subRoutes.termsOfService.routerLink;
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) public data: UserAccountRegistrationDialogParams, @Inject(MAT_DIALOG_DATA) public data: UserAccountRegistrationDialogParams,
private dataService: DataService private dataService: DataService,
private destroyRef: DestroyRef
) { ) {
addIcons({ arrowForwardOutline, checkmarkOutline, copyOutline }); addIcons({ arrowForwardOutline, checkmarkOutline, copyOutline });
} }
@ -77,7 +75,7 @@ export class GfUserAccountRegistrationDialogComponent implements OnDestroy {
public createAccount() { public createAccount() {
this.dataService this.dataService
.postUser() .postUser()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ accessToken, authToken, role }) => { .subscribe(({ accessToken, authToken, role }) => {
this.accessToken = accessToken; this.accessToken = accessToken;
this.authToken = authToken; this.authToken = authToken;
@ -96,9 +94,4 @@ export class GfUserAccountRegistrationDialogComponent implements OnDestroy {
public onChangeDislaimerChecked() { public onChangeDislaimerChecked() {
this.isDisclaimerChecked = !this.isDisclaimerChecked; this.isDisclaimerChecked = !this.isDisclaimerChecked;
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

2
libs/common/src/lib/permissions.ts

@ -195,7 +195,7 @@ export function hasReadRestrictedAccessPermission({
return false; return false;
} }
const access = user.accessesGet?.find(({ id }) => { const access = user?.accessesGet?.find(({ id }) => {
return id === impersonationId; return id === impersonationId;
}); });

Loading…
Cancel
Save