diff --git a/libs/common/src/lib/routes/interfaces/internal-route.interface.ts b/libs/common/src/lib/routes/interfaces/internal-route.interface.ts
index f08cf8b5c..8240db46a 100644
--- a/libs/common/src/lib/routes/interfaces/internal-route.interface.ts
+++ b/libs/common/src/lib/routes/interfaces/internal-route.interface.ts
@@ -5,5 +5,5 @@ export interface InternalRoute {
path?: string;
routerLink: string[];
subRoutes?: Record
;
- title: string;
+ title?: string;
}
diff --git a/libs/common/src/lib/routes/routes.ts b/libs/common/src/lib/routes/routes.ts
index 2785efdde..8132520fc 100644
--- a/libs/common/src/lib/routes/routes.ts
+++ b/libs/common/src/lib/routes/routes.ts
@@ -153,8 +153,7 @@ export const internalRoutes = {
webauthn: {
excludeFromAssistant: true,
path: 'webauthn',
- routerLink: ['/webauthn'],
- title: $localize`Sign in`
+ routerLink: ['/webauthn']
},
zen: {
excludeFromAssistant: true,
From 029e714b614ff5002db9fb823d4672150f0c94f7 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Jul 2026 09:38:53 +0200
Subject: [PATCH 23/30] Task/upgrade helmet to version 8.2.0 (#7300)
* Update helmet to version 8.2.0
* Update changelog
---
CHANGELOG.md | 6 +++++-
package-lock.json | 13 ++++++++-----
package.json | 2 +-
3 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2a5d24e2..1cfb0f18f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-### Unreleased
+## Unreleased
+
+### Changed
+
+- Upgraded `helmet` from version `7.0.0` to `8.2.0`
### Fixed
diff --git a/package-lock.json b/package-lock.json
index 051e79839..fababe870 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -72,7 +72,7 @@
"fast-redact": "3.5.0",
"fuse.js": "7.3.0",
"google-spreadsheet": "3.2.0",
- "helmet": "7.0.0",
+ "helmet": "8.2.0",
"http-status-codes": "2.3.0",
"ionicons": "8.0.13",
"jsonpath": "1.3.0",
@@ -21897,12 +21897,15 @@
}
},
"node_modules/helmet": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.0.0.tgz",
- "integrity": "sha512-MsIgYmdBh460ZZ8cJC81q4XJknjG567wzEmv46WOBblDb6TUd3z8/GhgmsM9pn8g2B80tAJ4m5/d3Bi1KrSUBQ==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz",
+ "integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==",
"license": "MIT",
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/EvanHahn"
}
},
"node_modules/hono": {
diff --git a/package.json b/package.json
index 4c400b8af..1f377c3e1 100644
--- a/package.json
+++ b/package.json
@@ -116,7 +116,7 @@
"fast-redact": "3.5.0",
"fuse.js": "7.3.0",
"google-spreadsheet": "3.2.0",
- "helmet": "7.0.0",
+ "helmet": "8.2.0",
"http-status-codes": "2.3.0",
"ionicons": "8.0.13",
"jsonpath": "1.3.0",
From 6c74aeb4360de92786e641dec82a4d85125a38f9 Mon Sep 17 00:00:00 2001
From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com>
Date: Sun, 12 Jul 2026 14:50:41 +0700
Subject: [PATCH 24/30] Task/improve type safety in multiple client components
(#7302)
Improve type safety
---
.../home-holdings/home-holdings.component.ts | 51 +++++++-----
.../user-account-settings.component.ts | 81 ++++++++++---------
.../user-detail-dialog.component.ts | 4 +-
3 files changed, 73 insertions(+), 63 deletions(-)
diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts
index a789f3c66..cf7e52833 100644
--- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts
+++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts
@@ -20,6 +20,7 @@ import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
+ inject,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -53,32 +54,34 @@ import { DeviceDetectorService } from 'ngx-device-detector';
export class GfHomeHoldingsComponent implements OnInit {
public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE';
- public deviceType: string;
- public hasImpersonationId: boolean;
- public hasPermissionToAccessHoldingsChart: boolean;
- public hasPermissionToCreateActivity: boolean;
- public holdings: PortfolioPosition[];
- public holdingType: HoldingType = 'ACTIVE';
- public holdingTypeOptions: ToggleOption[] = [
+ protected deviceType: string;
+ protected hasImpersonationId: boolean;
+ protected hasPermissionToAccessHoldingsChart: boolean;
+ protected hasPermissionToCreateActivity: boolean;
+ protected holdings: PortfolioPosition[];
+ protected holdingType: HoldingType = 'ACTIVE';
+ protected readonly holdingTypeOptions: ToggleOption[] = [
{ label: $localize`Active`, value: 'ACTIVE' },
{ label: $localize`Closed`, value: 'CLOSED' }
];
- public routerLinkPortfolioActivities =
+ protected readonly routerLinkPortfolioActivities =
internalRoutes.portfolio.subRoutes.activities.routerLink;
- public user: User;
- public viewModeFormControl = new FormControl(
+ protected user: User;
+ protected readonly viewModeFormControl = new FormControl(
GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE
);
- public constructor(
- private changeDetectorRef: ChangeDetectorRef,
- private dataService: DataService,
- private destroyRef: DestroyRef,
- private deviceDetectorService: DeviceDetectorService,
- private impersonationStorageService: ImpersonationStorageService,
- private router: Router,
- private userService: UserService
- ) {
+ private readonly changeDetectorRef = inject(ChangeDetectorRef);
+ private readonly dataService = inject(DataService);
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly deviceDetectorService = inject(DeviceDetectorService);
+ private readonly impersonationStorageService = inject(
+ ImpersonationStorageService
+ );
+ private readonly router = inject(Router);
+ private readonly userService = inject(UserService);
+
+ public constructor() {
addIcons({ gridOutline, reorderFourOutline });
}
@@ -119,6 +122,10 @@ export class GfHomeHoldingsComponent implements OnInit {
this.viewModeFormControl.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((holdingsViewMode) => {
+ if (!holdingsViewMode) {
+ return;
+ }
+
this.dataService
.putUserSetting({ holdingsViewMode })
.pipe(takeUntilDestroyed(this.destroyRef))
@@ -135,13 +142,13 @@ export class GfHomeHoldingsComponent implements OnInit {
});
}
- public onChangeHoldingType(aHoldingType: HoldingType) {
+ protected onChangeHoldingType(aHoldingType: HoldingType) {
this.holdingType = aHoldingType;
this.initialize();
}
- public onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) {
+ protected onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) {
if (dataSource && symbol) {
this.router.navigate([], {
queryParams: { dataSource, symbol, holdingDetailDialog: true }
@@ -185,7 +192,7 @@ export class GfHomeHoldingsComponent implements OnInit {
);
}
- this.holdings = undefined;
+ this.holdings = [];
this.fetchHoldings()
.pipe(takeUntilDestroyed(this.destroyRef))
diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts
index c8210b0c3..c9626b188 100644
--- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts
+++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts
@@ -20,11 +20,12 @@ import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
+ inject,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
- FormBuilder,
+ NonNullableFormBuilder,
FormsModule,
ReactiveFormsModule,
Validators
@@ -69,22 +70,22 @@ import { catchError } from 'rxjs/operators';
templateUrl: './user-account-settings.html'
})
export class GfUserAccountSettingsComponent implements OnInit {
- public appearancePlaceholder = $localize`Auto`;
- public baseCurrency: string;
- public closeUserAccountMail: string;
- public currencies: string[] = [];
- public deleteOwnUserForm = this.formBuilder.group({
+ protected readonly appearancePlaceholder = $localize`Auto`;
+ protected readonly baseCurrency: string;
+ protected closeUserAccountMail: string;
+ protected readonly currencies: string[] = [];
+ protected readonly deleteOwnUserForm = inject(NonNullableFormBuilder).group({
accessToken: ['', Validators.required]
});
- public hasPermissionToDeleteOwnUser: boolean;
- public hasPermissionToRequestOwnUserDeletion: boolean;
- public hasPermissionToUpdateViewMode: boolean;
- public hasPermissionToUpdateUserSettings: boolean;
- public isAccessTokenHidden = true;
- public isFingerprintSupported = this.doesBrowserSupportAuthn();
- public isWebAuthnEnabled: boolean;
- public language = document.documentElement.lang;
- public locales = [
+ protected hasPermissionToDeleteOwnUser: boolean;
+ protected hasPermissionToRequestOwnUserDeletion: boolean;
+ protected hasPermissionToUpdateViewMode: boolean;
+ protected hasPermissionToUpdateUserSettings: boolean;
+ protected isAccessTokenHidden = true;
+ protected readonly isFingerprintSupported = this.doesBrowserSupportAuthn();
+ protected isWebAuthnEnabled: boolean;
+ protected readonly language = document.documentElement.lang;
+ protected locales = [
'ca',
'de',
'de-CH',
@@ -102,19 +103,18 @@ export class GfUserAccountSettingsComponent implements OnInit {
'uk',
'zh'
];
- public user: User;
-
- public constructor(
- private changeDetectorRef: ChangeDetectorRef,
- private dataService: DataService,
- private destroyRef: DestroyRef,
- private formBuilder: FormBuilder,
- private notificationService: NotificationService,
- private settingsStorageService: SettingsStorageService,
- private snackBar: MatSnackBar,
- private userService: UserService,
- public webAuthnService: WebAuthnService
- ) {
+ protected user: User;
+
+ private readonly changeDetectorRef = inject(ChangeDetectorRef);
+ private readonly dataService = inject(DataService);
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly notificationService = inject(NotificationService);
+ private readonly settingsStorageService = inject(SettingsStorageService);
+ private readonly snackBar = inject(MatSnackBar);
+ private readonly userService = inject(UserService);
+ private readonly webAuthnService = inject(WebAuthnService);
+
+ public constructor() {
const { baseCurrency, currencies } = this.dataService.fetchInfo();
this.baseCurrency = baseCurrency;
@@ -148,7 +148,10 @@ export class GfUserAccountSettingsComponent implements OnInit {
permissions.updateViewMode
);
- this.locales.push(this.user.settings.locale);
+ if (this.user.settings.locale) {
+ this.locales.push(this.user.settings.locale);
+ }
+
this.locales = Array.from(new Set(this.locales)).sort();
this.changeDetectorRef.markForCheck();
@@ -162,11 +165,11 @@ export class GfUserAccountSettingsComponent implements OnInit {
this.update();
}
- public isCommunityLanguage() {
+ protected isCommunityLanguage() {
return !['de', 'en'].includes(this.language);
}
- public onChangeUserSetting(aKey: string, aValue: string) {
+ protected onChangeUserSetting(aKey: string, aValue: string) {
this.dataService
.putUserSetting({ [aKey]: aValue })
.pipe(takeUntilDestroyed(this.destroyRef))
@@ -190,12 +193,12 @@ export class GfUserAccountSettingsComponent implements OnInit {
});
}
- public onCloseAccount() {
+ protected onCloseAccount() {
this.notificationService.confirm({
confirmFn: () => {
this.dataService
.deleteOwnUser({
- accessToken: this.deleteOwnUserForm.get('accessToken').value
+ accessToken: this.deleteOwnUserForm.controls.accessToken.value
})
.pipe(
catchError(() => {
@@ -218,7 +221,7 @@ export class GfUserAccountSettingsComponent implements OnInit {
});
}
- public onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) {
+ protected onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) {
this.dataService
.putUserSetting({ isExperimentalFeatures: aEvent.checked })
.pipe(takeUntilDestroyed(this.destroyRef))
@@ -234,13 +237,13 @@ export class GfUserAccountSettingsComponent implements OnInit {
});
}
- public onExport() {
+ protected onExport() {
this.dataService
.fetchExport()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((data) => {
for (const activity of data.activities) {
- delete activity.id;
+ delete (activity as Omit & { id?: string }).id;
}
downloadAsFile({
@@ -254,7 +257,7 @@ export class GfUserAccountSettingsComponent implements OnInit {
});
}
- public onRestrictedViewChange(aEvent: MatSlideToggleChange) {
+ protected onRestrictedViewChange(aEvent: MatSlideToggleChange) {
this.dataService
.putUserSetting({ isRestrictedView: aEvent.checked })
.pipe(takeUntilDestroyed(this.destroyRef))
@@ -270,7 +273,7 @@ export class GfUserAccountSettingsComponent implements OnInit {
});
}
- public async onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) {
+ protected async onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) {
if (aEvent.checked) {
try {
await this.registerDevice();
@@ -293,7 +296,7 @@ export class GfUserAccountSettingsComponent implements OnInit {
}
}
- public onViewModeChange(aEvent: MatSlideToggleChange) {
+ protected onViewModeChange(aEvent: MatSlideToggleChange) {
this.dataService
.putUserSetting({ viewMode: aEvent.checked === true ? 'ZEN' : 'DEFAULT' })
.pipe(takeUntilDestroyed(this.destroyRef))
diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
index 637fe8c31..04d89c5a6 100644
--- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
+++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
@@ -49,7 +49,7 @@ import {
templateUrl: './user-detail-dialog.html'
})
export class GfUserDetailDialogComponent implements OnInit {
- protected baseCurrency: string;
+ protected readonly baseCurrency: string;
protected readonly getCountryName = getCountryName;
protected readonly subscriptionsDataSource =
new MatTableDataSource();
@@ -114,7 +114,7 @@ export class GfUserDetailDialogComponent implements OnInit {
return price !== null;
})
.map(({ price }) => {
- return new Big(price);
+ return new Big(price ?? 0);
})
).toNumber();
}
From 55e3bd00d2f35732b9fed49326db93fac1267121 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Jul 2026 10:08:40 +0200
Subject: [PATCH 25/30] Task/change default data source of fear and greed index
stocks to MANUAL (#7307)
* Change default value to MANUAL
* Update changelog
---
CHANGELOG.md | 5 +++++
apps/api/src/services/configuration/configuration.service.ts | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1cfb0f18f..e5189deef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- Changed the default value of the `DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS` environment variable from `RAPID_API` to `MANUAL`
- Upgraded `helmet` from version `7.0.0` to `8.2.0`
### Fixed
@@ -16,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed the display of assets without a currency in the search results of the assistant
- Fixed the display of assets without a currency in the symbol autocomplete component
+### Todo
+
+- **Breaking Change**: Set the environment variable `DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS=RAPID_API` to keep using _Rapid API_ as the data source of the _Fear & Greed Index_ (market mood)
+
## 3.24.0 - 2026-07-11
### Added
diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts
index 497dc6040..3c152d5df 100644
--- a/apps/api/src/services/configuration/configuration.service.ts
+++ b/apps/api/src/services/configuration/configuration.service.ts
@@ -57,7 +57,7 @@ export class ConfigurationService {
CACHE_TTL: num({ default: CACHE_TTL_NO_CACHE }),
DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }),
DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS: str({
- default: DataSource.RAPID_API
+ default: DataSource.MANUAL
}),
DATA_SOURCE_IMPORT: str({ default: DataSource.YAHOO }),
DATA_SOURCES: json({
From 59baec0accb5f72ff4648cc7f3e010ad9747fa43 Mon Sep 17 00:00:00 2001
From: Cole Munz <145523609+munzzyy@users.noreply.github.com>
Date: Sun, 12 Jul 2026 09:12:16 -0500
Subject: [PATCH 26/30] Task/improve language localization for NL (#7296)
* Update translations
* Update changelog
---
CHANGELOG.md | 1 +
apps/client/src/locales/messages.nl.xlf | 28 ++++++++++++-------------
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5189deef..178a1c7d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Changed the default value of the `DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS` environment variable from `RAPID_API` to `MANUAL`
+- Improved the language localization for Dutch (`nl`)
- Upgraded `helmet` from version `7.0.0` to `8.2.0`
### Fixed
diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf
index 537bb8fdf..1654368f8 100644
--- a/apps/client/src/locales/messages.nl.xlf
+++ b/apps/client/src/locales/messages.nl.xlf
@@ -451,7 +451,7 @@
Find an account...
- Find an account...
+ Zoek een account...
libs/ui/src/lib/assistant/assistant.component.ts
447
@@ -1415,7 +1415,7 @@
The value has been copied to the clipboard
- The value has been copied to the clipboard
+ De waarde is naar het klembord gekopieerd
libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts
46
@@ -1499,7 +1499,7 @@
Copy
- Copy
+ Kopiëren
libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html
20
@@ -1723,7 +1723,7 @@
popular
- popular
+ populair
apps/client/src/app/components/admin-settings/admin-settings.component.html
79
@@ -2883,7 +2883,7 @@
Close Account
- Close Account
+ Account Sluiten
apps/client/src/app/components/user-account-settings/user-account-settings.html
337
@@ -4403,7 +4403,7 @@
For security reasons, please delete all activities and accounts first before your Ghostfolio account can be closed.
- For security reasons, please delete all activities and accounts first before your Ghostfolio account can be closed.
+ Verwijder om veiligheidsredenen eerst alle activiteiten en rekeningen voordat uw Ghostfolio account kan worden gesloten.
apps/client/src/app/components/user-account-settings/user-account-settings.html
348
@@ -4863,7 +4863,7 @@
The source code is fully available as open source software (OSS) under the AGPL-3.0 license
- De broncode is volledig beschikbaar als open source software (OSS) onder de AGPL-3.0-licentie
+ De broncode is volledig beschikbaar als open source software (OSS) onder de AGPL-3.0-licentie
apps/client/src/app/pages/about/overview/about-overview-page.html
16
@@ -4951,7 +4951,7 @@
At Ghostfolio, transparency is at the core of our values. We publish the source code as open source software (OSS) under the AGPL-3.0 license and we openly share aggregated key metrics of the platform’s operational status.
- Bij Ghostfolio is transparantie één van onze kernwaarden. We publiceren de broncode als open source software (OSS) onder de AGPL-3.0 licentie en we delen openlijk geaggregeerde kerncijfers van de operationele status van het platform.
+ Bij Ghostfolio is transparantie één van onze kernwaarden. We publiceren de broncode als open source software (OSS) onder de AGPL-3.0 licentie en we delen openlijk geaggregeerde kerncijfers van de operationele status van het platform.
apps/client/src/app/pages/open/open-page.html
7
@@ -7094,7 +7094,7 @@
Tax Reporting
- Tax Reporting
+ Belastingrapportage
libs/ui/src/lib/i18n.ts
112
@@ -7218,7 +7218,7 @@
is Open Source Software
- is Open Source Software
+ is open source software
apps/client/src/app/pages/resources/personal-finance-tools/product-page.html
183
@@ -7258,7 +7258,7 @@
to use our referral link and get a Ghostfolio Premium membership for one year
- to use our referral link and get a Ghostfolio Premium membership for one year
+ om onze referral link te gebruiken en een jaar lang Ghostfolio Premium-lidmaatschap te krijgen
apps/client/src/app/pages/pricing/pricing-page.html
340
@@ -7676,7 +7676,7 @@
Net Worth Tracking
- Net Worth Tracking
+ Netto waarde volgen
libs/ui/src/lib/i18n.ts
110
@@ -8296,7 +8296,7 @@
Post to Ghostfolio on X (formerly Twitter)
- Post to Ghostfolio on X (formerly Twitter)
+ Post naar Ghostfolio op X (voorheen Twitter)
apps/client/src/app/pages/about/overview/about-overview-page.html
85
@@ -8533,7 +8533,7 @@
If you encounter a bug, would like to suggest an improvement or a new feature, please join the Ghostfolio Slack community, post to @ghostfolio_
- Als je een bug tegenkomt, een verbetering wilt voorstellen of een nieuwe functie wilt toevoegen, word dan lid van de Ghostfolio Slack-community. Stuur een bericht naar @ghostfolio_
+ Als je een bug tegenkomt, een verbetering wilt voorstellen of een nieuwe functie wilt toevoegen, word dan lid van de Ghostfolio Slack-community. Stuur een bericht naar @ghostfolio_
apps/client/src/app/pages/about/overview/about-overview-page.html
71
From 00d908c7e295965fdd133772e8454367735a49a1 Mon Sep 17 00:00:00 2001
From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com>
Date: Sun, 12 Jul 2026 21:22:15 +0700
Subject: [PATCH 27/30] Task/improve type safety in create or update activity
dialog component (#7294)
* fix(client): resolve type errors
* feat(client): enforce encapsulation
* feat(client): replace constructor based DI with inject functions
* feat(client): enforce immutability
* fix(client): resolve type error in import activities dialog
---
...ate-or-update-activity-dialog.component.ts | 88 ++++++++++---------
.../import-activities-dialog.component.ts | 2 +-
2 files changed, 48 insertions(+), 42 deletions(-)
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 ba17dbef3..4dbf6ad9f 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
@@ -20,7 +20,7 @@ import {
ChangeDetectorRef,
Component,
DestroyRef,
- Inject
+ inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
@@ -75,42 +75,46 @@ import { ActivityType } from './types/activity-type.type';
templateUrl: 'create-or-update-activity-dialog.html'
})
export class GfCreateOrUpdateActivityDialogComponent {
- public activityForm: FormGroup;
-
- public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass)
- .map((id) => {
- return { id, label: translate(id) } as AssetClassSelectorOption;
- })
- .sort((a, b) => {
- return a.label.localeCompare(b.label);
- });
+ protected activityForm: FormGroup;
- public assetSubClassOptions: AssetClassSelectorOption[] = [];
- public currencies: string[] = [];
- public currencyOfAssetProfile: string | undefined;
- public currentMarketPrice: number | null = null;
- public defaultDateFormat: string;
- public defaultLookupItems: LookupItem[] = [];
- public hasPermissionToCreateOwnTag: boolean | undefined;
- public isLoading = false;
- public isToday = isToday;
- public mode: 'create' | 'update';
- public tagsAvailable: Tag[] = [];
- public total = 0;
- public typesTranslationMap = new Map();
- public Validators = Validators;
-
- public constructor(
- private changeDetectorRef: ChangeDetectorRef,
- @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams,
- private dataService: DataService,
- private dateAdapter: DateAdapter,
- private destroyRef: DestroyRef,
- public dialogRef: MatDialogRef,
- private formBuilder: FormBuilder,
- @Inject(MAT_DATE_LOCALE) private locale: string,
- private userService: UserService
- ) {
+ protected readonly assetClassOptions: AssetClassSelectorOption[] =
+ Object.keys(AssetClass)
+ .map((id) => {
+ return { id, label: translate(id) } as AssetClassSelectorOption;
+ })
+ .sort((a, b) => {
+ return a.label.localeCompare(b.label);
+ });
+
+ protected assetSubClassOptions: AssetClassSelectorOption[] = [];
+ protected currencies: string[] = [];
+ protected currencyOfAssetProfile: string | undefined;
+ protected currentMarketPrice: number | null = null;
+ protected defaultDateFormat: string;
+ protected defaultLookupItems: LookupItem[] = [];
+ protected hasPermissionToCreateOwnTag: boolean | undefined;
+ protected isLoading = false;
+ protected readonly isToday = isToday;
+ protected mode: 'create' | 'update';
+ protected tagsAvailable: Tag[] = [];
+ protected total = 0;
+ protected readonly typesTranslationMap = new Map();
+ protected readonly Validators = Validators;
+
+ protected readonly data =
+ inject(MAT_DIALOG_DATA);
+
+ private readonly changeDetectorRef = inject(ChangeDetectorRef);
+ private readonly dataService = inject(DataService);
+ private readonly dateAdapter = inject>(DateAdapter);
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly dialogRef =
+ inject>(MatDialogRef);
+ private readonly formBuilder = inject(FormBuilder);
+ private locale = inject(MAT_DATE_LOCALE);
+ private readonly userService = inject(UserService);
+
+ public constructor() {
addIcons({ calendarClearOutline, refreshOutline });
}
@@ -138,7 +142,9 @@ export class GfCreateOrUpdateActivityDialogComponent {
return !['CASH'].includes(assetProfile.assetSubClass);
})
.sort((a, b) => {
- return a.assetProfile.name?.localeCompare(b.assetProfile.name);
+ return (a.assetProfile.name ?? '').localeCompare(
+ b.assetProfile.name ?? ''
+ );
})
.map(({ assetProfile }) => {
return {
@@ -463,14 +469,14 @@ export class GfCreateOrUpdateActivityDialogComponent {
}
}
- public applyCurrentMarketPrice() {
+ protected applyCurrentMarketPrice() {
this.activityForm.patchValue({
currencyOfUnitPrice: this.activityForm.get('currency')?.value,
unitPrice: this.currentMarketPrice
});
}
- public dateFilter(aDate: Date) {
+ protected dateFilter(aDate: Date) {
if (!aDate) {
return true;
}
@@ -478,11 +484,11 @@ export class GfCreateOrUpdateActivityDialogComponent {
return isAfter(aDate, new Date(0));
}
- public onCancel() {
+ protected onCancel() {
this.dialogRef.close();
}
- public async onSubmit() {
+ protected async onSubmit() {
const activity: CreateOrderDto | UpdateOrderDto = {
accountId: this.activityForm.get('accountId')?.value,
assetClass: this.activityForm.get('assetClass')?.value,
diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
index c3dbe6cf2..4541009a0 100644
--- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
+++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
@@ -151,7 +151,7 @@ export class GfImportActivitiesDialogComponent {
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ holdings }) => {
this.holdings = sortBy(holdings, ({ assetProfile }) => {
- return assetProfile.name.toLowerCase();
+ return assetProfile.name?.toLowerCase();
});
this.assetProfileForm.controls.assetProfileIdentifier.enable();
From eaa5965f1b6ba4a5fd8a9c47e11411f7a4a1ad2d Mon Sep 17 00:00:00 2001
From: Red Rose <146128882+RED-ROSE515@users.noreply.github.com>
Date: Sun, 12 Jul 2026 10:10:04 -0500
Subject: [PATCH 28/30] Bugfix/truncate long labels in page tabs (#7178)
* Truncate long labels
* Update changelog
---
CHANGELOG.md | 1 +
apps/client/src/styles.scss | 8 ++++++--
.../src/lib/page-tabs/page-tabs.component.html | 16 +++++++++++-----
.../src/lib/page-tabs/page-tabs.component.scss | 5 +++++
4 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 178a1c7d9..895acc2f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
+- Fixed the layout of the page tabs component by truncating long labels
- Fixed the display of assets without a currency in the search results of the assistant
- Fixed the display of assets without a currency in the symbol autocomplete component
diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss
index 045de2eb6..653efe376 100644
--- a/apps/client/src/styles.scss
+++ b/apps/client/src/styles.scss
@@ -376,12 +376,16 @@ ngx-skeleton-loader {
visibility: hidden;
}
+.lead {
+ font-weight: unset;
+}
+
.line-height-1 {
line-height: 1;
}
-.lead {
- font-weight: unset;
+.line-height-normal {
+ line-height: normal;
}
.mat-mdc-button-base {
diff --git a/libs/ui/src/lib/page-tabs/page-tabs.component.html b/libs/ui/src/lib/page-tabs/page-tabs.component.html
index 2898bcc79..34a5a3e26 100644
--- a/libs/ui/src/lib/page-tabs/page-tabs.component.html
+++ b/libs/ui/src/lib/page-tabs/page-tabs.component.html
@@ -45,9 +45,15 @@
-
-
+
+
+
+
diff --git a/libs/ui/src/lib/page-tabs/page-tabs.component.scss b/libs/ui/src/lib/page-tabs/page-tabs.component.scss
index ffb3ff33c..246a6147d 100644
--- a/libs/ui/src/lib/page-tabs/page-tabs.component.scss
+++ b/libs/ui/src/lib/page-tabs/page-tabs.component.scss
@@ -56,6 +56,11 @@
padding: 2rem 0;
width: 14rem;
+ .mat-mdc-tab-list,
+ .mat-mdc-tab-links {
+ width: 100%;
+ }
+
.mat-mdc-tab-links {
flex-direction: column;
From 3fdf9d7e3485b144caf30521ce88100047038afc Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Jul 2026 17:37:45 +0200
Subject: [PATCH 29/30] Task/rename SymbolProfile to assetProfile in portfolio
order interface (#7310)
Rename SymbolProfile to assetProfile
---
.../calculator/portfolio-calculator.ts | 18 +++++------
.../calculator/roai/portfolio-calculator.ts | 30 ++++++++-----------
.../interfaces/portfolio-order.interface.ts | 8 ++---
3 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
index ab3f76703..e73e222ac 100644
--- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
+++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
@@ -141,9 +141,9 @@ export abstract class PortfolioCalculator {
}
return {
- SymbolProfile,
tags,
type,
+ assetProfile: SymbolProfile,
date: format(date, DATE_FORMAT),
fee: new Big(feeInAssetProfileCurrency),
feeInBaseCurrency: new Big(feeInBaseCurrency),
@@ -934,23 +934,23 @@ export abstract class PortfolioCalculator {
let lastTransactionPoint: TransactionPoint = null;
for (const {
+ assetProfile,
date,
fee,
feeInBaseCurrency,
quantity,
- SymbolProfile,
tags,
type,
unitPrice
} of this.activities) {
let currentTransactionPointItem: TransactionPointSymbol;
- const assetSubClass = SymbolProfile.assetSubClass;
- const currency = SymbolProfile.currency;
- const dataSource = SymbolProfile.dataSource;
+ const assetSubClass = assetProfile.assetSubClass;
+ const currency = assetProfile.currency;
+ const dataSource = assetProfile.dataSource;
const factor = getFactor(type);
- const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles
- const symbol = SymbolProfile.symbol;
+ const skipErrors = !!assetProfile.userId; // Skip errors for custom asset profiles
+ const symbol = assetProfile.symbol;
const oldAccumulatedSymbol = symbols[symbol];
@@ -1034,12 +1034,12 @@ export abstract class PortfolioCalculator {
'id'
);
- symbols[SymbolProfile.symbol] = currentTransactionPointItem;
+ symbols[symbol] = currentTransactionPointItem;
const items = lastTransactionPoint?.items ?? [];
const newItems = items.filter(({ symbol }) => {
- return symbol !== SymbolProfile.symbol;
+ return symbol !== assetProfile.symbol;
});
newItems.push(currentTransactionPointItem);
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
index d5efc4bf2..f79e5bc79 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
+++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
@@ -192,12 +192,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
// Clone orders to keep the original values in this.orders
let orders: PortfolioOrderItem[] = cloneDeep(
- this.activities.filter(({ SymbolProfile }) => {
- return SymbolProfile.symbol === symbol;
+ this.activities.filter(({ assetProfile }) => {
+ return assetProfile.symbol === symbol;
})
);
- const isCash = orders[0]?.SymbolProfile?.assetSubClass === 'CASH';
+ const isCash = orders[0]?.assetProfile?.assetSubClass === 'CASH';
if (orders.length <= 0) {
return {
@@ -299,32 +299,30 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
};
}
+ const assetProfile: PortfolioOrderItem['assetProfile'] = {
+ dataSource,
+ symbol,
+ assetSubClass: isCash ? 'CASH' : undefined
+ };
+
// Add a synthetic order at the start and the end date
orders.push({
+ assetProfile,
date: startDateString,
fee: new Big(0),
feeInBaseCurrency: new Big(0),
itemType: 'start',
quantity: new Big(0),
- SymbolProfile: {
- dataSource,
- symbol,
- assetSubClass: isCash ? 'CASH' : undefined
- },
type: 'BUY',
unitPrice: unitPriceAtStartDate
});
orders.push({
+ assetProfile,
date: endDateString,
fee: new Big(0),
feeInBaseCurrency: new Big(0),
itemType: 'end',
- SymbolProfile: {
- dataSource,
- symbol,
- assetSubClass: isCash ? 'CASH' : undefined
- },
quantity: new Big(0),
type: 'BUY',
unitPrice: unitPriceAtEndDate
@@ -357,15 +355,11 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
}
} else {
orders.push({
+ assetProfile,
date: dateString,
fee: new Big(0),
feeInBaseCurrency: new Big(0),
quantity: new Big(0),
- SymbolProfile: {
- dataSource,
- symbol,
- assetSubClass: isCash ? 'CASH' : undefined
- },
type: 'BUY',
unitPrice: marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice,
unitPriceFromMarketData:
diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts
index 2dbd68f12..8d90c5bc6 100644
--- a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts
+++ b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts
@@ -1,13 +1,13 @@
import { Activity } from '@ghostfolio/common/interfaces';
export interface PortfolioOrder extends Pick {
+ assetProfile: Pick<
+ Activity['SymbolProfile'],
+ 'assetSubClass' | 'currency' | 'dataSource' | 'name' | 'symbol' | 'userId'
+ >;
date: string;
fee: Big;
feeInBaseCurrency: Big;
quantity: Big;
- SymbolProfile: Pick<
- Activity['SymbolProfile'],
- 'assetSubClass' | 'currency' | 'dataSource' | 'name' | 'symbol' | 'userId'
- >;
unitPrice: Big;
}
From 2b2ff0b883d5874fb93ee7ffae93aa1e41e8457e Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Jul 2026 17:40:17 +0200
Subject: [PATCH 30/30] Release 3.25.0 (#7311)
---
CHANGELOG.md | 2 +-
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 895acc2f6..8e6752795 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## Unreleased
+## 3.25.0 - 2026-07-12
### Changed
diff --git a/package-lock.json b/package-lock.json
index fababe870..4c0b837ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ghostfolio",
- "version": "3.24.0",
+ "version": "3.25.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
- "version": "3.24.0",
+ "version": "3.25.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
diff --git a/package.json b/package.json
index 1f377c3e1..bc6b86836 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ghostfolio",
- "version": "3.24.0",
+ "version": "3.25.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",