From 9217ff587b0528e74cd733d3fac82498f4e554a3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:45:49 +0200 Subject: [PATCH 1/5] Task/harmonize subject of mailto links (#7268) Harmonize subject of mailto links --- .../components/admin-settings/admin-settings.component.html | 2 +- .../holding-detail-dialog/holding-detail-dialog.component.ts | 2 +- .../user-account-membership.component.ts | 2 +- apps/client/src/app/pages/faq/saas/saas-page.html | 2 +- apps/client/src/app/pages/pricing/pricing-page.html | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.html b/apps/client/src/app/components/admin-settings/admin-settings.component.html index 76af96c4e..8ee6250e5 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.html +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.html @@ -21,7 +21,7 @@ Get Access diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index b2cdedbc5..c5beb6c2d 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -456,7 +456,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.assetProfile?.symbol ? ` (${this.assetProfile.symbol})` : '' }`; - this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=${reportDataGlitchSubject}&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${this.assetProfile?.symbol}%0DData Source: ${this.assetProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; + this.reportDataGlitchMail = `mailto:hi@ghostfol.io?subject=${reportDataGlitchSubject}&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${this.assetProfile?.symbol}%0DData Source: ${this.assetProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; if (this.assetProfile?.assetClass) { this.assetClass = translate(this.assetProfile?.assetClass); diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts index 1b23a6df9..d086867aa 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts @@ -52,7 +52,7 @@ export class GfUserAccountMembershipComponent { public priceId: string; public routerLinkPricing = publicRoutes.pricing.routerLink; public trySubscriptionMail = - 'mailto:hi@ghostfol.io?Subject=Ghostfolio Premium Trial&body=Hello%0D%0DI am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?%0D%0DKind regards'; + 'mailto:hi@ghostfol.io?subject=Ghostfolio Premium Trial&body=Hello%0D%0DI am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?%0D%0DKind regards'; public user: User; public constructor( diff --git a/apps/client/src/app/pages/faq/saas/saas-page.html b/apps/client/src/app/pages/faq/saas/saas-page.html index e832676bf..fc39acc53 100644 --- a/apps/client/src/app/pages/faq/saas/saas-page.html +++ b/apps/client/src/app/pages/faq/saas/saas-page.html @@ -96,7 +96,7 @@ Request your student discount - here with + here with your university e-mail address. diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index 23693457c..ddd92892a 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -332,7 +332,7 @@ } please   - contact us   @@ -343,7 +343,7 @@   Request it   - here + here   with your university e-mail address.

From 6278cf6691c5f09c7a5df81d0580330064948a0e Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Wed, 8 Jul 2026 02:47:38 +0700 Subject: [PATCH 2/5] Task/improve type safety in portfolio performance component (#7265) Improve type safety --- .../home-overview/home-overview.html | 1 - .../portfolio-performance.component.html | 22 ++-- .../portfolio-performance.component.ts | 103 ++++++++++-------- 3 files changed, 68 insertions(+), 58 deletions(-) diff --git a/apps/client/src/app/components/home-overview/home-overview.html b/apps/client/src/app/components/home-overview/home-overview.html index 8361c5b88..0ca4912b9 100644 --- a/apps/client/src/app/components/home-overview/home-overview.html +++ b/apps/client/src/app/components/home-overview/home-overview.html @@ -86,7 +86,6 @@
- @if (errors?.length > 0 && !isLoading) { + @if (errors()?.length > 0 && !isLoading()) { }
- @if (isLoading) { + @if (isLoading()) {
- {{ unit }} + {{ unit() }}
- @if (showDetails) { + @if (showDetails()) {
@@ -50,11 +50,11 @@
diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts index 56e75ec1e..a48d77a2d 100644 --- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts +++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts @@ -13,10 +13,11 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, Component, + effect, ElementRef, - Input, - OnChanges, - ViewChild + inject, + input, + viewChild } from '@angular/core'; import { IonIcon } from '@ionic/angular/standalone'; import { CountUp } from 'countup.js'; @@ -32,58 +33,68 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; styleUrls: ['./portfolio-performance.component.scss'], templateUrl: './portfolio-performance.component.html' }) -export class GfPortfolioPerformanceComponent implements OnChanges { - @Input() deviceType: string; - @Input() errors: ResponseError['errors']; - @Input() isLoading: boolean; - @Input() locale = getLocale(); - @Input() performance: PortfolioPerformance; - @Input() precision: number; - @Input() showDetails: boolean; - @Input() unit: string; +export class GfPortfolioPerformanceComponent { + public readonly errors = input(); + public readonly isLoading = input(); + public readonly locale = input(getLocale()); + public readonly performance = input.required(); + public readonly precision = input.required({ + transform: (value) => { + return value >= 0 ? value : 2; + } + }); + public readonly showDetails = input(false); + public readonly unit = input.required(); - @ViewChild('value') value: ElementRef; + private readonly value = + viewChild.required>('value'); - public constructor(private notificationService: NotificationService) { - addIcons({ timeOutline }); - } + private readonly notificationService = inject(NotificationService); - public ngOnChanges() { - this.precision = this.precision >= 0 ? this.precision : 2; + public constructor() { + addIcons({ timeOutline }); - if (this.isLoading) { - if (this.value?.nativeElement) { - this.value.nativeElement.innerHTML = ''; - } - } else { - if (isNumber(this.performance?.currentValueInBaseCurrency)) { - new CountUp('value', this.performance?.currentValueInBaseCurrency, { - decimal: getNumberFormatDecimal(this.locale), - decimalPlaces: this.precision, - duration: 1, - separator: getNumberFormatGroup(this.locale) - }).start(); - } else if (this.showDetails === false) { - new CountUp( - 'value', - this.performance?.netPerformancePercentageWithCurrencyEffect * 100, - { - decimal: getNumberFormatDecimal(this.locale), - decimalPlaces: 2, - duration: 1, - separator: getNumberFormatGroup(this.locale) - } - ).start(); + effect(() => { + if (this.isLoading()) { + if (this.value().nativeElement) { + this.value().nativeElement.innerHTML = ''; + } } else { - this.value.nativeElement.innerHTML = '*****'; + if (isNumber(this.performance().currentValueInBaseCurrency)) { + new CountUp('value', this.performance().currentValueInBaseCurrency, { + decimal: getNumberFormatDecimal(this.locale()), + decimalPlaces: this.precision(), + duration: 1, + separator: getNumberFormatGroup(this.locale()) + }).start(); + } else if (this.showDetails() === false) { + new CountUp( + 'value', + this.performance().netPerformancePercentageWithCurrencyEffect * 100, + { + decimal: getNumberFormatDecimal(this.locale()), + decimalPlaces: 2, + duration: 1, + separator: getNumberFormatGroup(this.locale()) + } + ).start(); + } else { + this.value().nativeElement.innerHTML = '*****'; + } } - } + }); } - public onShowErrors() { - const errorMessageParts = []; + protected onShowErrors() { + const errors = this.errors(); + + if (!errors?.length) { + return; + } + + const errorMessageParts: string[] = []; - for (const error of this.errors) { + for (const error of errors) { errorMessageParts.push(`${error.symbol} (${error.dataSource})`); } From 9d98799b47aa8182d21155cae85bbf68262a749f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:48:25 +0200 Subject: [PATCH 3/5] Task/reorder updatedAt field in symbol profile data model (#7270) Reorder updatedAt --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 233a63688..ed07d6f47 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -203,11 +203,11 @@ model SymbolProfile { isActive Boolean @default(true) isin String? name String? - updatedAt DateTime @updatedAt scraperConfiguration Json? sectors Json? symbol String symbolMapping Json? + updatedAt DateTime @updatedAt url String? user User? @relation(fields: [userId], onDelete: Cascade, references: [id]) userId String? From e9c48de4909034a9e118196fd6b6517706fb1e60 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:17:55 -0600 Subject: [PATCH 4/5] Task/set change detection strategy to OnPush in users of admin control panel (#7266) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 1 + .../src/app/components/admin-users/admin-users.component.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e968ebdba..6d127948f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Set the change detection strategy to `OnPush` in the _FIRE_ page +- Set the change detection strategy to `OnPush` in the users section of the admin control panel - Improved the language localization for Dutch (`nl`) - Improved the language localization for French (`fr`) - Improved the language localization for German (`de`) diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 5460745f5..4b9848cf8 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -27,6 +27,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -67,6 +68,7 @@ import { interval } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, GfPremiumIndicatorComponent, @@ -165,6 +167,8 @@ export class GfAdminUsersComponent implements OnInit { this.user.permissions, permissions.impersonateAllUsers ); + + this.changeDetectorRef.markForCheck(); } }), switchMap(() => this.route.paramMap) @@ -196,6 +200,8 @@ export class GfAdminUsersComponent implements OnInit { pageIndex: this.paginator().pageIndex, showLoading: false }); + + this.changeDetectorRef.markForCheck(); }); } From 45ab4016e521f7e9978a3c92eb93b9f24d1a4a74 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:28:43 +0200 Subject: [PATCH 5/5] Task/improve user account deletion flow (#7269) * Improve user account deletion flow * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/user/user.service.ts | 8 ++ .../user-account-settings.component.ts | 9 ++ .../user-account-settings.html | 110 +++++++++++------- libs/common/src/lib/permissions.ts | 1 + 5 files changed, 84 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d127948f..c111ac3c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the user account deletion flow in the user settings of the user account page - Set the change detection strategy to `OnPush` in the _FIRE_ page - Set the change detection strategy to `OnPush` in the users section of the admin control panel - Improved the language localization for Dutch (`nl`) diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 0c159bc1c..4dcf16034 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -535,6 +535,14 @@ export class UserService { user.subscription.offer.label = undefined; } + if ( + !hasRole(user, Role.DEMO) && + (user.provider !== 'ANONYMOUS' || + user.subscription?.type === SubscriptionType.Premium) + ) { + currentPermissions.push(permissions.requestOwnUserDeletion); + } + if (hasRole(user, Role.ADMIN)) { currentPermissions.push(permissions.syncDemoUserAccount); } 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 948c5a25e..c8210b0c3 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 @@ -71,11 +71,13 @@ import { catchError } from 'rxjs/operators'; export class GfUserAccountSettingsComponent implements OnInit { public appearancePlaceholder = $localize`Auto`; public baseCurrency: string; + public closeUserAccountMail: string; public currencies: string[] = []; public deleteOwnUserForm = this.formBuilder.group({ accessToken: ['', Validators.required] }); public hasPermissionToDeleteOwnUser: boolean; + public hasPermissionToRequestOwnUserDeletion: boolean; public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateUserSettings: boolean; public isAccessTokenHidden = true; @@ -124,11 +126,18 @@ export class GfUserAccountSettingsComponent implements OnInit { if (state?.user) { this.user = state.user; + this.closeUserAccountMail = `mailto:hi@ghostfol.io?subject=Delete Account&body=Hello%0D%0DPlease delete my Ghostfolio account.%0D%0DUser ID: ${this.user.id}%0D%0DKind regards`; + this.hasPermissionToDeleteOwnUser = hasPermission( this.user.permissions, permissions.deleteOwnUser ); + this.hasPermissionToRequestOwnUserDeletion = hasPermission( + this.user.permissions, + permissions.requestOwnUserDeletion + ); + this.hasPermissionToUpdateUserSettings = hasPermission( this.user.permissions, permissions.updateUserSettings diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html index cefa3a0a1..351e718ec 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.html +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -280,61 +280,81 @@
-
- @if (hasPermissionToDeleteOwnUser) { + @if ( + hasPermissionToDeleteOwnUser || hasPermissionToRequestOwnUserDeletion + ) {
-
-
-
Danger Zone
-
- +
Danger Zone
+
+ @if (hasPermissionToDeleteOwnUser) { + - Security Token - - + + - - -
+
+ For security reasons, please delete all activities and accounts + first before your Ghostfolio account can be closed. +
+ } @else if (hasPermissionToRequestOwnUserDeletion) { + Close Account + }
- +
}
diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index f9cb19562..428de1788 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -50,6 +50,7 @@ export const permissions = { readTags: 'readTags', readWatchlist: 'readWatchlist', reportDataGlitch: 'reportDataGlitch', + requestOwnUserDeletion: 'requestOwnUserDeletion', syncDemoUserAccount: 'syncDemoUserAccount', toggleReadOnlyMode: 'toggleReadOnlyMode', updateAccount: 'updateAccount',