From ee59449d2994a3f7cea23f987b9f584014000d0e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 5 Jul 2026 20:58:12 +0200 Subject: [PATCH 01/71] Task/reorder attributes in cash portfolio calculator test (#7254) Reorder attributes --- .../calculator/roai/portfolio-calculator-cash.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index 7774ee629..589168989 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -148,15 +148,15 @@ describe('PortfolioCalculator', () => { balances: [ { accountId, - id: randomUUID(), date: parseDate('2023-12-31'), + id: randomUUID(), value: 1000, valueInBaseCurrency: 850 }, { accountId, - id: randomUUID(), date: parseDate('2024-12-31'), + id: randomUUID(), value: 2000, valueInBaseCurrency: 1800 } From 898fc76965efe0096d2d76f48cffbf9155b1a7f0 Mon Sep 17 00:00:00 2001 From: Archit Goyal Date: Mon, 6 Jul 2026 00:53:23 +0530 Subject: [PATCH 02/71] Task/set change detection strategy to OnPush in FIRE page (#7252) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 6 ++++++ .../src/app/pages/portfolio/fire/fire-page.component.ts | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df95d849b..12df7a33c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Changed + +- Set the change detection strategy to `OnPush` in the _FIRE_ page + ## 3.21.0 - 2026-07-05 ### Added diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 04165ab11..e7b694819 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -14,6 +14,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -29,6 +30,7 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, FormsModule, @@ -89,6 +91,7 @@ export class GfFirePageComponent implements OnInit { : 0 } }; + if (this.user.subscription?.type === SubscriptionType.Basic) { this.fireWealth = { today: { @@ -107,6 +110,8 @@ export class GfFirePageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + + this.changeDetectorRef.markForCheck(); }); this.safeWithdrawalRateControl.valueChanges @@ -135,9 +140,9 @@ export class GfFirePageComponent implements OnInit { ); this.calculateWithdrawalRates(); - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); } From cdd6ed0aeb4bd974adc85c9561e640da2f1064e7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 6 Jul 2026 07:41:31 +0200 Subject: [PATCH 03/71] Task/improve language localization for DE (20260705 - Part III) (#7256) * Update translation * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.de.xlf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12df7a33c..d8d63de05 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 - Set the change detection strategy to `OnPush` in the _FIRE_ page +- Improved the language localization for German (`de`) ## 3.21.0 - 2026-07-05 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 9181a1d91..1d63e4ddc 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -5649,7 +5649,7 @@ Ready to take your investments to the next level? - Bereit, deine Investitionen auf ein neues Levelzu bringen? + Bereit, deine Investitionen auf ein neues Level zu bringen? apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 369 From 0b94f87f3261b0e04f39998d6f9bf303783ca47f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:41:35 +0200 Subject: [PATCH 04/71] Feature/extend alert dialog with copy-to-clipboard functionality (#7258) * Extend alert dialog with copy-to-clipboard functionality * Update changelog --- CHANGELOG.md | 4 +++ .../user-account-membership.component.ts | 6 ++-- .../alert-dialog/alert-dialog.component.ts | 29 ++++++++++++++++++- .../alert-dialog/alert-dialog.html | 16 ++++++++-- .../alert-dialog/interfaces/interfaces.ts | 1 + .../notifications/interfaces/interfaces.ts | 1 + .../lib/notifications/notification.service.ts | 1 + 7 files changed, 51 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8d63de05..944a3b704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for a copy-to-clipboard action in the alert dialog component + ### Changed - Set the change detection strategy to `OnPush` in the _FIRE_ page 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 b13a983fc..1b23a6df9 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 @@ -146,11 +146,9 @@ export class GfUserAccountMembershipComponent { ) .subscribe(({ apiKey }) => { this.notificationService.alert({ + copyValue: apiKey, discardLabel: $localize`Okay`, - message: - $localize`Set this API key in your self-hosted environment:` + - '
' + - apiKey, + message: $localize`Set this API key in your self-hosted environment:`, title: $localize`Ghostfolio Premium Data Provider API Key` }); }); diff --git a/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts index 3dbe70387..16fb4b47b 100644 --- a/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts +++ b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts @@ -1,6 +1,9 @@ +import { Clipboard } from '@angular/cdk/clipboard'; import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import ms from 'ms'; import { AlertDialogParams } from './interfaces/interfaces'; @@ -12,6 +15,7 @@ import { AlertDialogParams } from './interfaces/interfaces'; templateUrl: './alert-dialog.html' }) export class GfAlertDialogComponent { + public copyValue?: string; public discardLabel: string; public message?: string; public title: string; @@ -19,9 +23,32 @@ export class GfAlertDialogComponent { protected readonly dialogRef = inject>(MatDialogRef); - public initialize({ discardLabel, message, title }: AlertDialogParams) { + private readonly clipboard = inject(Clipboard); + private readonly snackBar = inject(MatSnackBar); + + public initialize({ + copyValue, + discardLabel, + message, + title + }: AlertDialogParams) { + this.copyValue = copyValue; this.discardLabel = discardLabel; this.message = message; this.title = title; } + + public onCopyToClipboard() { + if (this.copyValue) { + this.clipboard.copy(this.copyValue); + + this.snackBar.open( + '✅ ' + $localize`The value has been copied to the clipboard`, + undefined, + { + duration: ms('3 seconds') + } + ); + } + } } diff --git a/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html index 6602078d3..f690a8e5d 100644 --- a/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html +++ b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html @@ -2,10 +2,22 @@
} -@if (message) { -
+@if (message || copyValue) { +
+ @if (message) { +
+ } + @if (copyValue) { +
{{ copyValue }}
+ } +
}
+ @if (copyValue) { + + }
diff --git a/libs/ui/src/lib/notifications/alert-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/alert-dialog/interfaces/interfaces.ts index fa330c463..7b2c16e52 100644 --- a/libs/ui/src/lib/notifications/alert-dialog/interfaces/interfaces.ts +++ b/libs/ui/src/lib/notifications/alert-dialog/interfaces/interfaces.ts @@ -1,4 +1,5 @@ export interface AlertDialogParams { + copyValue?: string; discardLabel: string; message?: string; title: string; diff --git a/libs/ui/src/lib/notifications/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/interfaces/interfaces.ts index 071597691..dcea7d53c 100644 --- a/libs/ui/src/lib/notifications/interfaces/interfaces.ts +++ b/libs/ui/src/lib/notifications/interfaces/interfaces.ts @@ -1,6 +1,7 @@ import { ConfirmationDialogType } from '@ghostfolio/common/enums'; export interface AlertParams { + copyValue?: string; discardFn?: () => void; discardLabel?: string; message?: string; diff --git a/libs/ui/src/lib/notifications/notification.service.ts b/libs/ui/src/lib/notifications/notification.service.ts index b9a2562f7..389f52180 100644 --- a/libs/ui/src/lib/notifications/notification.service.ts +++ b/libs/ui/src/lib/notifications/notification.service.ts @@ -31,6 +31,7 @@ export class NotificationService { }); dialog.componentInstance.initialize({ + copyValue: aParams.copyValue, discardLabel: aParams.discardLabel, message: aParams.message, title: aParams.title From 602605ca20b353b391dae36795cb773de00260b7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:56:21 +0200 Subject: [PATCH 05/71] Task/sort categories and platforms in personal finance tools (#7255) Sort categories and platforms --- .../interfaces/interfaces.ts | 6 +++ .../product-page.component.ts | 38 +++++++++++++------ .../personal-finance-tools/product-page.html | 8 ++-- 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 apps/client/src/app/pages/resources/personal-finance-tools/interfaces/interfaces.ts diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/interfaces/interfaces.ts b/apps/client/src/app/pages/resources/personal-finance-tools/interfaces/interfaces.ts new file mode 100644 index 000000000..738719f0a --- /dev/null +++ b/apps/client/src/app/pages/resources/personal-finance-tools/interfaces/interfaces.ts @@ -0,0 +1,6 @@ +import type { Product } from '@ghostfolio/common/interfaces'; + +export type ResolvedProduct = Omit & { + categories?: string[]; + platforms?: string[]; +}; diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts index b030521db..d33ec17d6 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -1,5 +1,4 @@ import { getCountryName } from '@ghostfolio/common/helper'; -import { Product } from '@ghostfolio/common/interfaces'; import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { translate } from '@ghostfolio/ui/i18n'; @@ -14,6 +13,8 @@ import { import { MatButtonModule } from '@angular/material/button'; import { ActivatedRoute, RouterModule } from '@angular/router'; +import { ResolvedProduct } from './interfaces/interfaces'; + @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, @@ -28,8 +29,12 @@ export class GfProductPageComponent { return subscriptionOffer?.price; }); - protected readonly product1 = computed(() => ({ - categories: ['FINANCIAL_PLANNING', 'NET_WORTH_TRACKING', 'STOCK_TRACKING'], + protected readonly product1 = computed(() => ({ + categories: this.getSortedTranslations([ + 'FINANCIAL_PLANNING', + 'NET_WORTH_TRACKING', + 'STOCK_TRACKING' + ]), founded: 2021, hasFreePlan: true, hasSelfHostingAbility: true, @@ -50,21 +55,23 @@ export class GfProductPageComponent { ], name: 'Ghostfolio', origin: getCountryName({ code: 'CH' }), - platforms: ['ANDROID', 'WEB'], + platforms: this.getSortedTranslations(['ANDROID', 'WEB']), regions: [$localize`Global`], slogan: 'Open Source Wealth Management', useAnonymously: true })); - protected readonly product2 = computed(() => { + protected readonly product2 = computed(() => { const product = personalFinanceTools.find(({ key }) => { return key === this.route.snapshot.data['key']; }); - const mappedProduct = { + const mappedProduct: ResolvedProduct = { key: product?.key ?? '', name: product?.name ?? '', - ...product + ...product, + categories: this.getSortedTranslations(product?.categories), + platforms: this.getSortedTranslations(product?.platforms) }; if (mappedProduct.origin) { @@ -97,9 +104,8 @@ export class GfProductPageComponent { ...[product1, product2].flatMap( ({ categories, name, origin, platforms }) => { return [ - ...[...(categories ?? []), ...(platforms ?? [])].map((key) => { - return translate(key); - }), + ...(categories ?? []), + ...(platforms ?? []), name, origin ]; @@ -130,8 +136,16 @@ export class GfProductPageComponent { }); }); - protected readonly translate = translate; - private readonly dataService = inject(DataService); private readonly route = inject(ActivatedRoute); + + private getSortedTranslations(values?: string[]) { + return values + ?.map((value) => { + return translate(value); + }) + .sort((a, b) => { + return a.localeCompare(b, undefined, { sensitivity: 'base' }); + }); + } } diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html index 9a89c941f..192b3cadb 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -81,7 +81,7 @@ track category; let isLast = $last ) { - {{ translate(category) }}{{ isLast ? '' : ', ' }} + {{ category }}{{ isLast ? '' : ', ' }} } @@ -90,7 +90,7 @@ track category; let isLast = $last ) { - {{ translate(category) }}{{ isLast ? '' : ', ' }} + {{ category }}{{ isLast ? '' : ', ' }} } @@ -135,7 +135,7 @@ track platform; let isLast = $last ) { - {{ translate(platform) }}{{ isLast ? '' : ', ' }} + {{ platform }}{{ isLast ? '' : ', ' }} } @@ -144,7 +144,7 @@ track platform; let isLast = $last ) { - {{ translate(platform) }}{{ isLast ? '' : ', ' }} + {{ platform }}{{ isLast ? '' : ', ' }} } From 8aa476909bee09f3c2d44e7cdd714edeee418cdc Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:57:47 +0200 Subject: [PATCH 06/71] Task/extend coupons with createdAt (#7257) * Extend coupons with createdAt --- .../admin-overview.component.ts | 8 ++++++- .../admin-overview/admin-overview.html | 22 +++++++++++++++++++ .../src/lib/interfaces/coupon.interface.ts | 3 ++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index b2c9b11a3..7374fb05c 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -90,7 +90,12 @@ export class GfAdminOverviewComponent implements OnInit { protected activitiesCount: number; protected couponDuration: StringValue = '14 days'; protected readonly couponsDataSource = new MatTableDataSource(); - protected readonly couponsDisplayedColumns = ['code', 'duration', 'actions']; + protected readonly couponsDisplayedColumns = [ + 'code', + 'duration', + 'createdAt', + 'actions' + ]; protected hasPermissionForSubscription: boolean; protected hasPermissionForSystemMessage: boolean; protected hasPermissionToSyncDemoUserAccount: boolean; @@ -201,6 +206,7 @@ export class GfAdminOverviewComponent implements OnInit { protected onAddCoupon() { const newCoupon: Coupon = { code: `${ghostfolioPrefix}${this.generateCouponCode(14)}`, + createdAt: new Date().toISOString(), duration: this.couponDuration }; diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html index bccedd251..2fde94083 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -180,6 +180,28 @@ + + + Creation + + + @if (element.createdAt) { + + } @else { + - + } + + + Date: Mon, 6 Jul 2026 15:10:50 -0500 Subject: [PATCH 07/71] Task/improve language localization for FR (#7261) * Update translations * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.fr.xlf | 330 ++++++++++++------------ 2 files changed, 166 insertions(+), 165 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 944a3b704..26502a243 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 +- Improved the language localization for French (`fr`) - Improved the language localization for German (`de`) ## 3.21.0 - 2026-07-05 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 05ecd4c4f..2272f3005 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -83,7 +83,7 @@
with - with + avec apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 87 @@ -507,7 +507,7 @@ Find an account... - Find an account... + Rechercher un compte... libs/ui/src/lib/assistant/assistant.component.ts 447 @@ -535,7 +535,7 @@ Watch the Ghostfol.io Trailer on YouTube - Watch the Ghostfol.io Trailer on YouTube + Regarder la bande-annonce de Ghostfol.io sur YouTube apps/client/src/app/pages/landing/landing-page.html 19 @@ -587,7 +587,7 @@ Data Gathering Frequency - Data Gathering Frequency + Fréquence de collecte des données apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 454 @@ -623,7 +623,7 @@ Apply current market price - Apply current market price + Appliquer le prix actuel du marché apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 238 @@ -631,7 +631,7 @@ Subscription History - Subscription History + Historique de l’abonnement apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 136 @@ -915,7 +915,7 @@ No auto-renewal on membership. - No auto-renewal on membership. + Pas de renouvellement automatique de l’abonnement. apps/client/src/app/components/user-account-membership/user-account-membership.html 74 @@ -943,7 +943,7 @@ Could not validate form - Could not validate form + Le formulaire n’a pas pu être validé apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 621 @@ -1119,7 +1119,7 @@ Code - Code + Code apps/client/src/app/components/admin-overview/admin-overview.html 159 @@ -1215,7 +1215,7 @@ Energy - Energy + Énergie libs/ui/src/lib/i18n.ts 94 @@ -1399,7 +1399,7 @@ Ghostfolio in Numbers: Monthly Active Users (MAU) - Ghostfolio in Numbers: Monthly Active Users (MAU) + Ghostfolio en chiffres : utilisateurs actifs mensuels (MAU) apps/client/src/app/pages/landing/landing-page.html 63 @@ -1419,7 +1419,7 @@ Performance with currency effect - Performance with currency effect + Performance avec effet de change apps/client/src/app/pages/portfolio/analysis/analysis-page.html 134 @@ -1571,7 +1571,7 @@ Consumer Defensive - Consumer Defensive + Consommation de Base libs/ui/src/lib/i18n.ts 93 @@ -1639,7 +1639,7 @@ Utilities - Utilities + Services aux Collectivités libs/ui/src/lib/i18n.ts 101 @@ -1707,7 +1707,7 @@ Contributors to Ghostfolio - Contributors to Ghostfolio + Contributeurs de Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 54 @@ -1867,7 +1867,7 @@ Financial Planning - Financial Planning + Planification Financière libs/ui/src/lib/i18n.ts 108 @@ -2035,7 +2035,7 @@ Duration - Duration + Durée apps/client/src/app/components/admin-overview/admin-overview.html 172 @@ -2111,7 +2111,7 @@ just now - just now + à l’instant apps/client/src/app/components/admin-users/admin-users.component.ts 211 @@ -2239,7 +2239,7 @@ Current week - Current week + Semaine en cours apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 220 @@ -2291,7 +2291,7 @@ Consumer Cyclical - Consumer Cyclical + Consommation Discrétionnaire libs/ui/src/lib/i18n.ts 92 @@ -2327,7 +2327,7 @@ or start a discussion at - or start a discussion at + ou lancez une discussion sur apps/client/src/app/pages/about/overview/about-overview-page.html 98 @@ -2363,7 +2363,7 @@ Price - Price + Prix apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 171 @@ -2479,7 +2479,7 @@ Trial - Trial + Essai apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 126 @@ -2487,7 +2487,7 @@ Exclude from Analysis - Exclude from Analysis + Exclure de l’analyse apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 99 @@ -2511,7 +2511,7 @@ Latest activities - Latest activities + Dernières activités apps/client/src/app/pages/public/public-page.html 210 @@ -2555,7 +2555,7 @@ Looking for a student discount? - Looking for a student discount? + Vous cherchez une réduction étudiant ? apps/client/src/app/pages/pricing/pricing-page.html 342 @@ -2595,7 +2595,7 @@ annual interest rate - annual interest rate + taux d’intérêt annuel apps/client/src/app/pages/portfolio/fire/fire-page.html 186 @@ -2723,7 +2723,7 @@ Sustainable retirement income - Sustainable retirement income + Revenu de retraite durable apps/client/src/app/pages/portfolio/fire/fire-page.html 42 @@ -2771,7 +2771,7 @@ Creation - Creation + Création apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3011,7 +3011,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} - {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} + {VAR_PLURAL, plural, =1 {Profil} other {Profils}} apps/client/src/app/components/admin-market-data/admin-market-data.html 249 @@ -3303,7 +3303,7 @@ Authentication - Authentication + Authentification apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 60 @@ -3383,7 +3383,7 @@ Communication Services - Communication Services + Services de Communication libs/ui/src/lib/i18n.ts 91 @@ -3407,7 +3407,7 @@ If you retire today, you would be able to withdraw - If you retire today, you would be able to withdraw + Si vous partez à la retraite aujourd’hui, vous pourriez retirer apps/client/src/app/pages/portfolio/fire/fire-page.html 69 @@ -3555,7 +3555,7 @@ No Activities - No Activities + Aucune Activité apps/client/src/app/components/admin-market-data/admin-market-data.component.ts 150 @@ -3571,7 +3571,7 @@ Everything in Basic, plus - Everything in Basic, plus + Tout ce qui est inclus dans Basic, plus apps/client/src/app/pages/pricing/pricing-page.html 199 @@ -3739,7 +3739,7 @@ Hourly - Hourly + Toutes les heures apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 214 @@ -3831,7 +3831,7 @@ Expiration - Expiration + Expiration apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 204 @@ -3847,7 +3847,7 @@ Could not save asset profile - Could not save asset profile + Le profil d’actif n’a pas pu être enregistré apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 655 @@ -3951,7 +3951,7 @@ Loan - Loan + Prêt libs/ui/src/lib/i18n.ts 64 @@ -4051,7 +4051,7 @@ Explore - Explore + Explorer apps/client/src/app/pages/resources/overview/resources-overview.component.html 11 @@ -4075,7 +4075,7 @@ Current year - Current year + Année en cours apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 228 @@ -4111,7 +4111,7 @@ Asset profile has been saved - Asset profile has been saved + Le profil d’actif a été enregistré apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 645 @@ -4303,7 +4303,7 @@ View Details - View Details + Voir les Détails apps/client/src/app/components/admin-users/admin-users.html 220 @@ -4439,7 +4439,7 @@ per week - per week + par semaine apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 130 @@ -4463,7 +4463,7 @@ Technology - Technology + Technologie libs/ui/src/lib/i18n.ts 100 @@ -4471,7 +4471,7 @@ and we share aggregated key metrics of the platform’s performance - and we share aggregated key metrics of the platform’s performance + et nous partageons des indicateurs clés agrégés de la performance de la plateforme apps/client/src/app/pages/about/overview/about-overview-page.html 33 @@ -4479,7 +4479,7 @@ Total - Total + Total apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 155 @@ -4523,7 +4523,7 @@ Website of Thomas Kaul - Website of Thomas Kaul + Site web de Thomas Kaul apps/client/src/app/pages/about/overview/about-overview-page.html 45 @@ -4599,7 +4599,7 @@ Upgrade to Ghostfolio Premium - Upgrade to Ghostfolio Premium + Passer à Ghostfolio Premium libs/ui/src/lib/premium-indicator/premium-indicator.component.html 4 @@ -4675,7 +4675,7 @@ Web - Web + Web libs/ui/src/lib/i18n.ts 120 @@ -4719,7 +4719,7 @@ Sign in with OpenID Connect - Sign in with OpenID Connect + Se connecter avec OpenID Connect apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 57 @@ -4747,7 +4747,7 @@ Category - Category + Catégorie apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 77 @@ -4779,7 +4779,7 @@ Ghostfolio in Numbers: Pulls on Docker Hub - Ghostfolio in Numbers: Pulls on Docker Hub + Ghostfolio en chiffres : téléchargements sur Docker Hub apps/client/src/app/pages/landing/landing-page.html 101 @@ -4891,7 +4891,7 @@ this is projected to increase to - this is projected to increase to + cela devrait augmenter jusqu’à apps/client/src/app/pages/portfolio/fire/fire-page.html 148 @@ -4943,7 +4943,7 @@ Job ID - Job ID + ID de Tâche apps/client/src/app/components/admin-jobs/admin-jobs.html 34 @@ -5027,7 +5027,7 @@ for - for + pour apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 128 @@ -5051,7 +5051,7 @@ Could not parse scraper configuration - Could not parse scraper configuration + La configuration du scraper n’a pas pu être analysée apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 569 @@ -5095,7 +5095,7 @@ Edit access - Edit access + Modifier l’accès apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 11 @@ -5151,7 +5151,7 @@ Basic Materials - Basic Materials + Matériaux de Base libs/ui/src/lib/i18n.ts 90 @@ -5367,7 +5367,7 @@ less than - less than + moins de apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -5609,7 +5609,7 @@ Stock Tracking - Stock Tracking + Suivi des Actions libs/ui/src/lib/i18n.ts 111 @@ -5653,7 +5653,7 @@ Ghostfolio Status - Ghostfolio Status + Statut de Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 64 @@ -5661,7 +5661,7 @@ with your university e-mail address - with your university e-mail address + avec votre adresse e-mail universitaire apps/client/src/app/pages/pricing/pricing-page.html 348 @@ -5681,7 +5681,7 @@ and a safe withdrawal rate (SWR) of - and a safe withdrawal rate (SWR) of + et un taux de retrait sûr (SWR) de apps/client/src/app/pages/portfolio/fire/fire-page.html 109 @@ -5689,7 +5689,7 @@ Available on - Available on + Disponible sur apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 130 @@ -5853,7 +5853,7 @@ Request it - Request it + Faites-en la demande apps/client/src/app/pages/pricing/pricing-page.html 344 @@ -5897,7 +5897,7 @@ Industrials - Industrials + Industrie libs/ui/src/lib/i18n.ts 97 @@ -5921,7 +5921,7 @@ , - , + , apps/client/src/app/pages/portfolio/fire/fire-page.html 146 @@ -5937,7 +5937,7 @@ per month - per month + par mois apps/client/src/app/pages/portfolio/fire/fire-page.html 95 @@ -6013,7 +6013,7 @@ Healthcare - Healthcare + Santé libs/ui/src/lib/i18n.ts 96 @@ -6029,7 +6029,7 @@ Portfolio Filters - Portfolio Filters + Filtres du Portefeuille apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 63 @@ -6197,7 +6197,7 @@ here - here + ici apps/client/src/app/pages/pricing/pricing-page.html 347 @@ -6205,7 +6205,7 @@ Close Holding - Close Holding + Clôturer la Position apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 451 @@ -6285,7 +6285,7 @@ Expires () - Expires () + Expire () apps/client/src/app/components/admin-users/admin-users.html 34 @@ -6313,7 +6313,7 @@ Fetch market price - Fetch market price + Récupérer le prix du marché libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html 40 @@ -6389,7 +6389,7 @@ Find a holding... - Find a holding... + Rechercher une position... libs/ui/src/lib/assistant/assistant.component.ts 448 @@ -6442,7 +6442,7 @@ Daily - Daily + Tous les jours apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 210 @@ -6570,7 +6570,7 @@ Jump to a page... - Jump to a page... + Aller à une page... libs/ui/src/lib/assistant/assistant.component.ts 449 @@ -6618,7 +6618,7 @@ Include in - Include in + Inclure dans apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 386 @@ -6854,7 +6854,7 @@ View Holding - View Holding + Voir la Position libs/ui/src/lib/activities-table/activities-table.component.html 475 @@ -6862,7 +6862,7 @@ Do you really want to delete these asset profiles? - Do you really want to delete these asset profiles? + Voulez-vous vraiment supprimer ces profils d’actifs ? apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 67 @@ -6902,7 +6902,7 @@ , based on your total assets of - , based on your total assets of + , basé sur le total de vos actifs de apps/client/src/app/pages/portfolio/fire/fire-page.html 97 @@ -7022,7 +7022,7 @@ Role - Role + Rôle apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 39 @@ -7046,7 +7046,7 @@ Tax Reporting - Tax Reporting + Déclaration Fiscale libs/ui/src/lib/i18n.ts 112 @@ -7070,7 +7070,7 @@ If you plan to open an account at - If you plan to open an account at + Si vous prévoyez d’ouvrir un compte chez apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7102,7 +7102,7 @@ send an e-mail to - send an e-mail to + envoyez un e-mail à apps/client/src/app/pages/about/overview/about-overview-page.html 91 @@ -7126,7 +7126,7 @@ has been copied to the clipboard - has been copied to the clipboard + a été copié dans le presse-papiers apps/client/src/app/components/admin-overview/admin-overview.component.ts 382 @@ -7146,7 +7146,7 @@ Compare Ghostfolio to - - Compare Ghostfolio to - + Comparer Ghostfolio à - apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html 32 @@ -7194,7 +7194,7 @@ , assuming a - , assuming a + , en supposant un apps/client/src/app/pages/portfolio/fire/fire-page.html 175 @@ -7202,7 +7202,7 @@ Financial Services - Financial Services + Services Financiers libs/ui/src/lib/i18n.ts 95 @@ -7230,7 +7230,7 @@ Delete - Delete + Supprimer apps/client/src/app/components/admin-market-data/admin-market-data.html 244 @@ -7286,7 +7286,7 @@ Dividend Tracking - Dividend Tracking + Suivi des Dividendes libs/ui/src/lib/i18n.ts 105 @@ -7306,7 +7306,7 @@ Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. - Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. + Ghostfolio est une application légère de gestion de patrimoine permettant de suivre des actions, ETF ou cryptomonnaies et de prendre des décisions d’investissement solides et basées sur les données. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7346,7 +7346,7 @@ Investment Research - Investment Research + Recherche d’Investissement libs/ui/src/lib/i18n.ts 109 @@ -7620,7 +7620,7 @@ Check the system status at - Check the system status at + Vérifiez le statut du système sur apps/client/src/app/pages/about/overview/about-overview-page.html 59 @@ -7628,7 +7628,7 @@ Net Worth Tracking - Net Worth Tracking + Suivi du Patrimoine Net libs/ui/src/lib/i18n.ts 110 @@ -7760,7 +7760,7 @@ Ghostfolio in Numbers: Stars on GitHub - Ghostfolio in Numbers: Stars on GitHub + Ghostfolio en chiffres : étoiles sur GitHub apps/client/src/app/pages/landing/landing-page.html 82 @@ -7796,7 +7796,7 @@ The project has been initiated by - The project has been initiated by + Le projet a été initié par apps/client/src/app/pages/about/overview/about-overview-page.html 41 @@ -7820,7 +7820,7 @@ Total amount - Total amount + Montant Total apps/client/src/app/pages/portfolio/analysis/analysis-page.html 94 @@ -8082,7 +8082,7 @@ ETF Tracking - ETF Tracking + Suivi des ETF libs/ui/src/lib/i18n.ts 106 @@ -8106,7 +8106,7 @@ Set up - Fonds d’urgence : Mise en place + Mise en place apps/client/src/app/pages/i18n/i18n-page.html 145 @@ -8130,7 +8130,7 @@ Fee Ratio - Fee Ratio + Ratio de Frais apps/client/src/app/pages/i18n/i18n-page.html 152 @@ -8138,7 +8138,7 @@ The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) - The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Les frais dépassent ${thresholdMax}% de votre volume d’investissement total (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 @@ -8146,7 +8146,7 @@ The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) - The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Les frais ne dépassent pas ${thresholdMax}% de votre volume d’investissement total (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8312,7 +8312,7 @@ Current month - Current month + Mois en cours apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 224 @@ -8320,7 +8320,7 @@ new - new + nouveau apps/client/src/app/components/admin-settings/admin-settings.component.html 79 @@ -8328,7 +8328,7 @@ Investment - Investment + Investissement apps/client/src/app/pages/i18n/i18n-page.html 15 @@ -8336,7 +8336,7 @@ Over ${thresholdMax}% of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) - Over ${thresholdMax}% of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) + Plus de ${thresholdMax}% de votre investissement actuel est chez ${maxAccountName} (${maxInvestmentRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 17 @@ -8344,7 +8344,7 @@ The major part of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) and does not exceed ${thresholdMax}% - The major part of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) and does not exceed ${thresholdMax}% + La majeure partie de votre investissement actuel est chez ${maxAccountName} (${maxInvestmentRatio}%) et n’excède pas ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 24 @@ -8384,7 +8384,7 @@ Fixed Income - Fixed Income + Revenu Fixe apps/client/src/app/pages/i18n/i18n-page.html 55 @@ -8392,7 +8392,7 @@ The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% - The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% + La part en revenu fixe de votre investissement actuel (${fixedIncomeValueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 57 @@ -8400,7 +8400,7 @@ The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% - The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% + La part en revenu fixe de votre investissement actuel (${fixedIncomeValueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 61 @@ -8408,7 +8408,7 @@ The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part en revenu fixe de votre investissement actuel (${fixedIncomeValueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 66 @@ -8416,7 +8416,7 @@ Investment: Base Currency - Investment: Base Currency + Investissement : Devise de Base apps/client/src/app/pages/i18n/i18n-page.html 85 @@ -8424,7 +8424,7 @@ The major part of your current investment is not in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) - The major part of your current investment is not in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) + La majeure partie de votre investissement actuel n’est pas dans votre devise de base (${baseCurrencyValueRatio}% en ${baseCurrency}) apps/client/src/app/pages/i18n/i18n-page.html 88 @@ -8432,7 +8432,7 @@ The major part of your current investment is in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) - The major part of your current investment is in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) + La majeure partie de votre investissement actuel est dans votre devise de base (${baseCurrencyValueRatio}% en ${baseCurrency}) apps/client/src/app/pages/i18n/i18n-page.html 92 @@ -8549,7 +8549,7 @@ Average Unit Price - Average Unit Price + Prix Unitaire Moyen apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts 136 @@ -8561,7 +8561,7 @@ Account Cluster Risks - Account Cluster Risks + Risques de Concentration par Compte apps/client/src/app/pages/i18n/i18n-page.html 14 @@ -8569,7 +8569,7 @@ Asset Class Cluster Risks - Asset Class Cluster Risks + Risques de Concentration par Classe d’Actifs apps/client/src/app/pages/i18n/i18n-page.html 39 @@ -8577,7 +8577,7 @@ Currency Cluster Risks - Currency Cluster Risks + Risques de Concentration par Devise apps/client/src/app/pages/i18n/i18n-page.html 83 @@ -8585,7 +8585,7 @@ Economic Market Cluster Risks - Economic Market Cluster Risks + Risques de Concentration par Marché Économique apps/client/src/app/pages/i18n/i18n-page.html 106 @@ -8593,7 +8593,7 @@ Emergency Fund - Emergency Fund + Fonds d’Urgence apps/client/src/app/pages/i18n/i18n-page.html 144 @@ -8601,7 +8601,7 @@ Fees - Fees + Frais apps/client/src/app/pages/i18n/i18n-page.html 161 @@ -8609,7 +8609,7 @@ Liquidity - Liquidity + Liquidités apps/client/src/app/pages/i18n/i18n-page.html 70 @@ -8617,7 +8617,7 @@ Buying Power - Buying Power + Pouvoir d’Achat apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8625,7 +8625,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Your buying power is below ${thresholdMin} ${baseCurrency} + Votre pouvoir d’achat est inférieur à ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8633,7 +8633,7 @@ Your buying power is 0 ${baseCurrency} - Your buying power is 0 ${baseCurrency} + Votre pouvoir d’achat est de 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8641,7 +8641,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Your buying power exceeds ${thresholdMin} ${baseCurrency} + Votre pouvoir d’achat dépasse ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8649,7 +8649,7 @@ Regional Market Cluster Risks - Regional Market Cluster Risks + Risques de Concentration par Marché Régional apps/client/src/app/pages/i18n/i18n-page.html 163 @@ -8657,7 +8657,7 @@ No results found... - No results found... + Aucun résultat trouvé... libs/ui/src/lib/assistant/assistant.html 51 @@ -8665,7 +8665,7 @@ Developed Markets - Developed Markets + Marchés développés apps/client/src/app/pages/i18n/i18n-page.html 109 @@ -8673,7 +8673,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% + La part des marchés développés de votre investissement actuel (${developedMarketsValueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 112 @@ -8681,7 +8681,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% + La part des marchés développés de votre investissement actuel (${developedMarketsValueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 117 @@ -8689,7 +8689,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part des marchés développés de votre investissement actuel (${developedMarketsValueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 122 @@ -8697,7 +8697,7 @@ Emerging Markets - Emerging Markets + Marchés émergents apps/client/src/app/pages/i18n/i18n-page.html 127 @@ -8705,7 +8705,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% + La part des marchés émergents de votre investissement actuel (${emergingMarketsValueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 130 @@ -8713,7 +8713,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% + La part des marchés émergents de votre investissement actuel (${emergingMarketsValueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 135 @@ -8721,7 +8721,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part des marchés émergents de votre investissement actuel (${emergingMarketsValueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 140 @@ -8729,7 +8729,7 @@ No accounts have been set up - No accounts have been set up + Aucun compte n’a été configuré apps/client/src/app/pages/i18n/i18n-page.html 21 @@ -8737,7 +8737,7 @@ Your net worth is managed by 0 accounts - Your net worth is managed by 0 accounts + Votre patrimoine est géré par 0 compte apps/client/src/app/pages/i18n/i18n-page.html 33 @@ -8745,7 +8745,7 @@ Asia-Pacific - Asia-Pacific + Asie-Pacifique apps/client/src/app/pages/i18n/i18n-page.html 165 @@ -8753,7 +8753,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La part du marché Asie-Pacifique de votre investissement actuel (${valueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 167 @@ -8761,7 +8761,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La part du marché Asie-Pacifique de votre investissement actuel (${valueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 171 @@ -8769,7 +8769,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part du marché Asie-Pacifique de votre investissement actuel (${valueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 175 @@ -8777,7 +8777,7 @@ Emerging Markets - Emerging Markets + Marchés émergents apps/client/src/app/pages/i18n/i18n-page.html 180 @@ -8809,7 +8809,7 @@ Europe - Europe + Europe apps/client/src/app/pages/i18n/i18n-page.html 195 @@ -8817,7 +8817,7 @@ The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La part du marché européen de votre investissement actuel (${valueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 197 @@ -8825,7 +8825,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La part du marché européen de votre investissement actuel (${valueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 201 @@ -8833,7 +8833,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part du marché européen de votre investissement actuel (${valueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 205 @@ -8841,7 +8841,7 @@ Japan - Japan + Japon apps/client/src/app/pages/i18n/i18n-page.html 209 @@ -8849,7 +8849,7 @@ The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La part du marché japonais de votre investissement actuel (${valueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 211 @@ -8857,7 +8857,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La part du marché japonais de votre investissement actuel (${valueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 215 @@ -8865,7 +8865,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part du marché japonais de votre investissement actuel (${valueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 219 @@ -8873,7 +8873,7 @@ North America - North America + Amérique du Nord apps/client/src/app/pages/i18n/i18n-page.html 223 @@ -8881,7 +8881,7 @@ The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + La part du marché nord-américain de votre investissement actuel (${valueRatio}%) dépasse ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 225 @@ -8889,7 +8889,7 @@ The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + La part du marché nord-américain de votre investissement actuel (${valueRatio}%) est inférieure à ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 229 @@ -8897,7 +8897,7 @@ The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + La part du marché nord-américain de votre investissement actuel (${valueRatio}%) se situe entre ${thresholdMin}% et ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 233 @@ -8905,7 +8905,7 @@ Find Ghostfolio on GitHub - Find Ghostfolio on GitHub + Trouver Ghostfolio sur GitHub apps/client/src/app/pages/about/overview/about-overview-page.html 20 @@ -8925,7 +8925,7 @@ Join the Ghostfolio Slack community - Join the Ghostfolio Slack community + Rejoindre la communauté Ghostfolio sur Slack apps/client/src/app/pages/about/overview/about-overview-page.html 78 @@ -8937,7 +8937,7 @@ Follow Ghostfolio on X (formerly Twitter) - Follow Ghostfolio on X (formerly Twitter) + Suivre Ghostfolio sur X (anciennement Twitter) apps/client/src/app/pages/about/overview/about-overview-page.html 122 @@ -8945,7 +8945,7 @@ Send an e-mail - Send an e-mail + Envoyer un e-mail apps/client/src/app/pages/about/overview/about-overview-page.html 93 @@ -8957,7 +8957,7 @@ Registration Date - Registration Date + Date d’Inscription apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 @@ -8965,7 +8965,7 @@ Follow Ghostfolio on LinkedIn - Follow Ghostfolio on LinkedIn + Suivre Ghostfolio sur LinkedIn apps/client/src/app/pages/about/overview/about-overview-page.html 151 @@ -8973,7 +8973,7 @@ Ghostfolio is an independent & bootstrapped business - Ghostfolio is an independent & bootstrapped business + Ghostfolio est une entreprise indépendante & autofinancée apps/client/src/app/pages/about/overview/about-overview-page.html 161 @@ -8981,7 +8981,7 @@ Support Ghostfolio - Support Ghostfolio + Soutenir Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 170 From 6372b3edf50c65faadcbd703bd114f87437a8ea6 Mon Sep 17 00:00:00 2001 From: Cole Munz <145523609+munzzyy@users.noreply.github.com> Date: Tue, 7 Jul 2026 01:09:08 -0500 Subject: [PATCH 08/71] Task/improve language localization for NL (#7260) * Update translations * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.nl.xlf | 114 ++++++++++++------------ 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26502a243..e968ebdba 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 +- 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/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 455745435..d0b61f75f 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -311,7 +311,7 @@ Paid - Paid + Betaald apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 122 @@ -387,7 +387,7 @@ and is driven by the efforts of its contributors - en wordt gedreven door de inspanningen van zijn bijdragers + en wordt gedreven door de inspanningen van zijn bijdragers apps/client/src/app/pages/about/overview/about-overview-page.html 50 @@ -479,7 +479,7 @@ Watch the Ghostfol.io Trailer on YouTube - Watch the Ghostfol.io Trailer on YouTube + Bekijk de Ghostfol.io-trailer op YouTube apps/client/src/app/pages/landing/landing-page.html 19 @@ -751,7 +751,7 @@ Creation - Creation + Aanmaakdatum apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -899,7 +899,7 @@ Energy - Energy + Energie libs/ui/src/lib/i18n.ts 94 @@ -1127,7 +1127,7 @@ Ghostfolio in Numbers: Monthly Active Users (MAU) - Ghostfolio in Numbers: Monthly Active Users (MAU) + Ghostfolio in cijfers: maandelijks actieve gebruikers (MAU) apps/client/src/app/pages/landing/landing-page.html 63 @@ -1251,7 +1251,7 @@ Consumer Defensive - Consumer Defensive + Basisconsumptiegoederen libs/ui/src/lib/i18n.ts 93 @@ -1319,7 +1319,7 @@ Utilities - Utilities + Nutsbedrijven libs/ui/src/lib/i18n.ts 101 @@ -1343,7 +1343,7 @@ Coupon - Coupon + Coupon apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 127 @@ -1563,7 +1563,7 @@ Financial Planning - Financial Planning + Financiële planning libs/ui/src/lib/i18n.ts 108 @@ -1707,7 +1707,7 @@ Duration - Duration + Looptijd apps/client/src/app/components/admin-overview/admin-overview.html 172 @@ -1923,7 +1923,7 @@ Trial - Trial + Proefperiode apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 126 @@ -2071,7 +2071,7 @@ Consumer Cyclical - Consumer Cyclical + Cyclische consumptiegoederen libs/ui/src/lib/i18n.ts 92 @@ -2467,7 +2467,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} - {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} + {VAR_PLURAL, plural, =1 {Profiel} other {Profielen}} apps/client/src/app/components/admin-market-data/admin-market-data.html 249 @@ -2707,7 +2707,7 @@ Apply current market price - Apply current market price + Huidige marktprijs toepassen apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 238 @@ -2715,7 +2715,7 @@ Subscription History - Subscription History + Abonnementsgeschiedenis apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 136 @@ -2867,7 +2867,7 @@ Contributors to Ghostfolio - Contributors to Ghostfolio + Bijdragers aan Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 54 @@ -2907,7 +2907,7 @@ Code - Code + Code apps/client/src/app/components/admin-overview/admin-overview.html 159 @@ -3215,7 +3215,7 @@ Communication Services - Communication Services + Communicatiediensten libs/ui/src/lib/i18n.ts 91 @@ -3275,7 +3275,7 @@ Price - Price + Prijs apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 171 @@ -3283,7 +3283,7 @@ Data Gathering Frequency - Data Gathering Frequency + Frequentie van gegevensverzameling apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 454 @@ -3447,7 +3447,7 @@ just now - just now + zojuist apps/client/src/app/components/admin-users/admin-users.component.ts 211 @@ -3739,7 +3739,7 @@ Hourly - Hourly + Elk uur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 214 @@ -3831,7 +3831,7 @@ Expiration - Expiration + Vervaldatum apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 204 @@ -3951,7 +3951,7 @@ Loan - Loan + Lening libs/ui/src/lib/i18n.ts 64 @@ -4051,7 +4051,7 @@ Explore - Explore + Verken apps/client/src/app/pages/resources/overview/resources-overview.component.html 11 @@ -4463,7 +4463,7 @@ Technology - Technology + Technologie libs/ui/src/lib/i18n.ts 100 @@ -4479,7 +4479,7 @@ Total - Total + Totaal apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 155 @@ -4599,7 +4599,7 @@ Upgrade to Ghostfolio Premium - Upgrade to Ghostfolio Premium + Upgraden naar Ghostfolio Premium libs/ui/src/lib/premium-indicator/premium-indicator.component.html 4 @@ -4675,7 +4675,7 @@ Web - Web + Web libs/ui/src/lib/i18n.ts 120 @@ -4747,7 +4747,7 @@ Category - Category + Categorie apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 77 @@ -4779,7 +4779,7 @@ Ghostfolio in Numbers: Pulls on Docker Hub - Ghostfolio in Numbers: Pulls on Docker Hub + Ghostfolio in cijfers: downloads op Docker Hub apps/client/src/app/pages/landing/landing-page.html 101 @@ -5151,7 +5151,7 @@ Basic Materials - Basic Materials + Basismaterialen libs/ui/src/lib/i18n.ts 90 @@ -5279,7 +5279,7 @@ Oops! Could not delete the asset profiles. - Oops! Could not delete the asset profiles. + Oeps! De activaprofielen konden niet worden verwijderd. apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 52 @@ -5609,7 +5609,7 @@ Stock Tracking - Stock Tracking + Aandelen volgen libs/ui/src/lib/i18n.ts 111 @@ -5689,7 +5689,7 @@ Available on - Available on + Beschikbaar op apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 130 @@ -5897,7 +5897,7 @@ Industrials - Industrials + Industrie libs/ui/src/lib/i18n.ts 97 @@ -6013,7 +6013,7 @@ Healthcare - Healthcare + Gezondheidszorg libs/ui/src/lib/i18n.ts 96 @@ -6029,7 +6029,7 @@ Portfolio Filters - Portfolio Filters + Portefeuillefilters apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 63 @@ -6285,7 +6285,7 @@ Expires () - Expires () + Verloopt () apps/client/src/app/components/admin-users/admin-users.html 34 @@ -6313,7 +6313,7 @@ Fetch market price - Fetch market price + Marktprijs ophalen libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html 40 @@ -6389,7 +6389,7 @@ Find a holding... - Find a holding... + Zoek een positie... libs/ui/src/lib/assistant/assistant.component.ts 448 @@ -6442,7 +6442,7 @@ Daily - Daily + Dagelijks apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 210 @@ -6570,7 +6570,7 @@ Jump to a page... - Jump to a page... + Ga naar een pagina... libs/ui/src/lib/assistant/assistant.component.ts 449 @@ -6758,7 +6758,7 @@ Oops! Could not delete the asset profile. - Oops! Could not delete the asset profile. + Oeps! Het activaprofiel kon niet worden verwijderd. apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 51 @@ -6862,7 +6862,7 @@ Do you really want to delete these asset profiles? - Do you really want to delete these asset profiles? + Wil je deze activaprofielen echt verwijderen? apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 67 @@ -7062,7 +7062,7 @@ Change with currency effect Change - Verandering met valuta effect Verandering + Verandering met valuta-effect Verandering apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 63 @@ -7078,7 +7078,7 @@ Performance with currency effect Performance - Prestatie met valuta effect Prestatie + Prestaties met valuta-effect Prestaties apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 83 @@ -7126,7 +7126,7 @@ has been copied to the clipboard - has been copied to the clipboard + is naar het klembord gekopieerd apps/client/src/app/components/admin-overview/admin-overview.component.ts 382 @@ -7146,7 +7146,7 @@ Compare Ghostfolio to - - Compare Ghostfolio to - + Vergelijk Ghostfolio met - apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html 32 @@ -7202,7 +7202,7 @@ Financial Services - Financial Services + Financiële diensten libs/ui/src/lib/i18n.ts 95 @@ -7230,7 +7230,7 @@ Delete - Delete + Verwijder apps/client/src/app/components/admin-market-data/admin-market-data.html 244 @@ -7286,7 +7286,7 @@ Dividend Tracking - Dividend Tracking + Dividend volgen libs/ui/src/lib/i18n.ts 105 @@ -7346,7 +7346,7 @@ Investment Research - Investment Research + Beleggingsonderzoek libs/ui/src/lib/i18n.ts 109 @@ -7620,7 +7620,7 @@ Check the system status at - Check the system status at + Controleer de systeemstatus op apps/client/src/app/pages/about/overview/about-overview-page.html 59 @@ -7760,7 +7760,7 @@ Ghostfolio in Numbers: Stars on GitHub - Ghostfolio in Numbers: Stars on GitHub + Ghostfolio in cijfers: sterren op GitHub apps/client/src/app/pages/landing/landing-page.html 82 @@ -8082,7 +8082,7 @@ ETF Tracking - ETF Tracking + ETF’s volgen libs/ui/src/lib/i18n.ts 106 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 09/71] 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 10/71] 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 11/71] 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 12/71] 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 13/71] 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', From 5841324910de90fb98bd69f26d84ace4c7ab036d Mon Sep 17 00:00:00 2001 From: KBS Date: Wed, 8 Jul 2026 05:50:40 +0900 Subject: [PATCH 14/71] Task/set change detection strategy to OnPush in portfolio holdings page (#7264) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 1 + .../components/home-holdings/home-holdings.component.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c111ac3c1..6c6137890 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 - Improved the user account deletion flow in the user settings of the user account page +- Set the change detection strategy to `OnPush` in the portfolio holdings 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/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index 661e21163..a789f3c66 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 @@ -15,6 +15,7 @@ import { GfToggleComponent } from '@ghostfolio/ui/toggle'; import { GfTreemapChartComponent } from '@ghostfolio/ui/treemap-chart'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, @@ -32,6 +33,7 @@ import { gridOutline, reorderFourOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ FormsModule, GfHoldingsTableComponent, @@ -88,6 +90,8 @@ export class GfHomeHoldingsComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + + this.changeDetectorRef.markForCheck(); }); this.userService.stateChanged @@ -107,9 +111,9 @@ export class GfHomeHoldingsComponent implements OnInit { ); this.initialize(); - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); this.viewModeFormControl.valueChanges From d68e13ab22c022a47472bdd6b8093a4711af949c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 09:18:25 +0200 Subject: [PATCH 15/71] Task/update locales (#7259) Co-authored-by: github-actions[bot] --- apps/client/src/locales/messages.ca.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.de.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.es.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.fr.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.it.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.ja.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.ko.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.nl.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.pl.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.pt.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.tr.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.uk.xlf | 130 ++++++++++++++++-------- apps/client/src/locales/messages.xlf | 126 +++++++++++++++-------- apps/client/src/locales/messages.zh.xlf | 130 ++++++++++++++++-------- 14 files changed, 1186 insertions(+), 630 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index c9f75afde..3ef04ff96 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -498,6 +498,14 @@ 62
+ + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Divisa @@ -623,7 +631,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1331,7 +1339,7 @@ Està segur qeu vol eliminar aquest cupó? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1339,7 +1347,7 @@ Està segur que vol eliminar aquest missatge del sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -1347,7 +1355,7 @@ Està segur que vol depurar el cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1355,7 +1363,7 @@ Si us plau, afegeixi el seu missatge del sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1371,7 +1379,7 @@ per Usuari apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1447,7 +1455,7 @@ Afegir apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1587,7 +1595,7 @@ Està segur que vol eliminar aquest usuari? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1695,7 +1703,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1791,7 +1799,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1871,7 +1879,7 @@ en Actiiu apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -1879,7 +1887,7 @@ Finalitzat apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -2091,7 +2099,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2183,7 +2191,7 @@ Les dades del mercat s’han retardat apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -2526,6 +2534,14 @@ 204 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Concedeix accés @@ -2579,7 +2595,7 @@ Introduïu el vostre codi de cupó. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -2587,7 +2603,7 @@ No s’ha pogut bescanviar el codi de cupó apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2603,7 +2619,7 @@ El codi del cupó s’ha bescanviat apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -2611,7 +2627,7 @@ Torna a carregar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2667,7 +2683,7 @@ De debò vols tancar el teu compte de Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -2683,7 +2699,7 @@ De debò vols eliminar aquest mètode d’inici de sessió? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -2699,7 +2715,7 @@ Ups! Hi ha hagut un error en configurar l’autenticació biomètrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -2778,6 +2794,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Aparença @@ -2870,7 +2898,7 @@ 255 - + Export Data Exporta dades @@ -2883,7 +2911,7 @@ Zona de perill apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -2891,7 +2919,7 @@ Tanca el compte apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -2943,7 +2971,7 @@ D’acord apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -3515,6 +3543,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Bons @@ -3638,6 +3674,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3676,7 +3716,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4772,7 +4812,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -5265,7 +5305,7 @@ Global apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6677,7 +6717,7 @@ Alternativa apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6685,7 +6725,7 @@ Aplicació apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6753,7 +6793,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6777,7 +6817,7 @@ Investor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6789,7 +6829,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6801,7 +6841,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6809,7 +6849,7 @@ Privacy apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6817,7 +6857,7 @@ Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6825,7 +6865,7 @@ Tool apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6833,7 +6873,7 @@ User Experience apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6841,7 +6881,7 @@ Wealth apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7129,7 +7169,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7507,7 +7547,7 @@ Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7515,7 +7555,7 @@ Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7871,7 +7911,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 1d63e4ddc..1d7d6b1b1 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -270,7 +270,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -538,7 +538,7 @@ Möchtest du diesen Gutscheincode wirklich löschen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -546,7 +546,7 @@ Möchtest du den Cache wirklich leeren? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -554,7 +554,7 @@ Bitte gebe deine Systemmeldung ein: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -562,7 +562,7 @@ pro Benutzer apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -638,7 +638,7 @@ Hinzufügen apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -666,7 +666,7 @@ Möchtest du diesen Benutzer wirklich löschen? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -768,6 +768,10 @@ Creation Erstellung + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -810,7 +814,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -850,7 +854,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -1186,7 +1190,7 @@ Okay apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -1254,7 +1258,7 @@ Bitte gebe deinen Gutscheincode ein. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -1262,7 +1266,7 @@ Gutscheincode konnte nicht eingelöst werden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -1278,7 +1282,7 @@ Gutscheincode wurde eingelöst apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -1286,7 +1290,7 @@ Neu laden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -1294,7 +1298,7 @@ Möchtest du diese Anmeldemethode wirklich löschen? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -1429,6 +1433,14 @@ 59 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Zugang gewähren @@ -1505,6 +1517,14 @@ 10 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Währung @@ -2274,7 +2294,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -2873,6 +2893,18 @@ 190 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Aussehen @@ -3466,7 +3498,7 @@ gerade eben apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4389,6 +4421,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Anleihen @@ -5013,7 +5053,7 @@ 133 - + Export Data Daten exportieren @@ -5660,7 +5700,7 @@ Weltweit apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6048,7 +6088,7 @@ Möchtest du diese Systemmeldung wirklich löschen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6192,7 +6232,7 @@ Die Marktdaten sind verzögert für apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6216,7 +6256,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6493,7 +6533,7 @@ Aktiv apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6501,7 +6541,7 @@ Abgeschlossen apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6589,7 +6629,7 @@ Möchtest du dieses Ghostfolio Konto wirklich schliessen? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6605,7 +6645,7 @@ Gefahrenzone apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6613,7 +6653,7 @@ Konto schliessen apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6653,7 +6693,7 @@ Ups! Beim Einrichten der biometrischen Authentifizierung ist ein Fehler aufgetreten. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6701,7 +6741,7 @@ Alternative apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6709,7 +6749,7 @@ App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6777,7 +6817,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6801,7 +6841,7 @@ Investor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6813,7 +6853,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6825,7 +6865,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6833,7 +6873,7 @@ Datenschutz apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6841,7 +6881,7 @@ Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6849,7 +6889,7 @@ Tool apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6857,7 +6897,7 @@ User Experience apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6865,7 +6905,7 @@ Vermögen apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7153,7 +7193,7 @@ wurde in die Zwischenablage kopiert apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7531,7 +7571,7 @@ API-Schlüssel für den Ghostfolio Premium Datenanbieter apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7539,7 +7579,7 @@ Möchtest du wirklich einen neuen API-Schlüssel erstellen? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7895,7 +7935,7 @@ Sicherheits-Token apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7907,7 +7947,7 @@ Möchtest du für diesen Benutzer wirklich ein neues Sicherheits-Token generieren? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Demo Benutzerkonto wurde synchronisiert. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index f459717b1..e99def710 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -271,7 +271,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -539,7 +539,7 @@ ¿Seguro que quieres eliminar este cupón? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -547,7 +547,7 @@ ¿Seguro que quieres limpiar la caché? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -555,7 +555,7 @@ Por favor, establece tu mensaje del sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -563,7 +563,7 @@ por usuario apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -623,7 +623,7 @@ Añadir apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -651,7 +651,7 @@ ¿Seguro que quieres eliminar este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -753,6 +753,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -795,7 +799,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -835,7 +839,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -1171,7 +1175,7 @@ De acuerdo apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -1239,7 +1243,7 @@ Por favor, ingresa tu código de cupón. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -1247,7 +1251,7 @@ No se pudo canjear el código de cupón apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -1263,7 +1267,7 @@ El código del cupón ha sido canjeado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -1271,7 +1275,7 @@ Recargar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -1279,7 +1283,7 @@ ¿Seguro que quieres eliminar este método de acceso? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -1414,6 +1418,14 @@ 59 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Conceder acceso @@ -1490,6 +1502,14 @@ 10 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Divisa @@ -2259,7 +2279,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -2858,6 +2878,18 @@ 190 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Apariencia @@ -3451,7 +3483,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4366,6 +4398,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Bonos @@ -4990,7 +5030,7 @@ 133 - + Export Data Exportar datos @@ -5637,7 +5677,7 @@ Global apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6025,7 +6065,7 @@ ¿Seguro que quieres eliminar este mensaje del sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6169,7 +6209,7 @@ Los datos del mercado tienen un retraso de apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6193,7 +6233,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6470,7 +6510,7 @@ Activo apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6478,7 +6518,7 @@ Cerrado apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6566,7 +6606,7 @@ ¿Seguro que quieres eliminar tu cuenta de Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6582,7 +6622,7 @@ Zona peligrosa apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6590,7 +6630,7 @@ Eliminar cuenta apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6630,7 +6670,7 @@ ¡Vaya! Hubo un error al configurar la autenticación biométrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6678,7 +6718,7 @@ Alternativa apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6686,7 +6726,7 @@ Aplicación apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6754,7 +6794,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6778,7 +6818,7 @@ Inversor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6790,7 +6830,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6802,7 +6842,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6810,7 +6850,7 @@ Privacidad apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6818,7 +6858,7 @@ Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6826,7 +6866,7 @@ Herramienta apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6834,7 +6874,7 @@ Experiencia del usuario apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6842,7 +6882,7 @@ Patrimonio apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7130,7 +7170,7 @@ ha sido copiado al portapapeles apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7508,7 +7548,7 @@ Clave API del proveedor de datos premium de Ghostfolio apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7516,7 +7556,7 @@ ¿Seguro que quieres generar una nueva clave API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7872,7 +7912,7 @@ Token de seguridad apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7884,7 +7924,7 @@ ¿Seguro que quieres generar un nuevo token de seguridad para este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8094,7 +8134,7 @@ La cuenta de usuario de demostración se ha sincronizado. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 2272f3005..119ba3d02 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -185,6 +185,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Devise @@ -326,7 +334,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -770,7 +778,7 @@ Voulez-vous vraiment supprimer ce code promotionnel ? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -778,7 +786,7 @@ Voulez-vous vraiment vider le cache ? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -786,7 +794,7 @@ Veuillez définir votre message système : apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -794,7 +802,7 @@ par Utilisateur apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -866,7 +874,7 @@ Ajouter apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -894,7 +902,7 @@ Voulez-vous vraiment supprimer cet·te utilisateur·rice ? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -978,7 +986,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1054,7 +1062,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1150,7 +1158,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -1478,7 +1486,7 @@ D’accord apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -1558,7 +1566,7 @@ Veuillez entrer votre code promotionnel. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -1566,7 +1574,7 @@ Le code promotionnel n’a pas pu être appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -1582,7 +1590,7 @@ Le code promotionnel a été appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -1590,7 +1598,7 @@ Rafraîchir apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -1598,7 +1606,7 @@ Voulez-vous vraiment supprimer cette méthode de connexion ? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -1697,6 +1705,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Apparence @@ -1781,6 +1801,14 @@ 59 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Donner accès @@ -2114,7 +2142,7 @@ à l’instant apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -2772,6 +2800,10 @@ Creation Création + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -4365,6 +4397,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Obligations @@ -4989,7 +5029,7 @@ 133 - + Export Data Exporter les Data @@ -5636,7 +5676,7 @@ Mondial apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6024,7 +6064,7 @@ Confirmer la suppresion de ce message système? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6168,7 +6208,7 @@ Les données du marché sont retardées de apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6192,7 +6232,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6469,7 +6509,7 @@ Actif apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6477,7 +6517,7 @@ Clôturé apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6565,7 +6605,7 @@ Confirmer la suppresion de votre compte Ghostfolio ? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6581,7 +6621,7 @@ Zone de danger apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6589,7 +6629,7 @@ Supprimer le compte apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6629,7 +6669,7 @@ Oops! Une erreur s’est produite lors de la configuration de l’authentification biométrique. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6677,7 +6717,7 @@ Alternative apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6685,7 +6725,7 @@ App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6753,7 +6793,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6777,7 +6817,7 @@ Investisseur apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6789,7 +6829,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6801,7 +6841,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6809,7 +6849,7 @@ Confidentialité apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6817,7 +6857,7 @@ Logiciels apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6825,7 +6865,7 @@ Outils apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6833,7 +6873,7 @@ Expérience Utilisateur apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6841,7 +6881,7 @@ Patrimoine apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7129,7 +7169,7 @@ a été copié dans le presse-papiers apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7507,7 +7547,7 @@ Clé API du fournisseur de données Ghostfolio Premium apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7515,7 +7555,7 @@ Voulez-vous vraiment générer une nouvelle clé API ? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7871,7 +7911,7 @@ Jeton de sécurité apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Voulez-vous vraiment générer un nouveau jeton de sécurité pour cet utilisateur ? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Le compte utilisateur de démonstration a été synchronisé. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index a8fd45524..66c141baa 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -271,7 +271,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -539,7 +539,7 @@ Vuoi davvero eliminare questo buono? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -547,7 +547,7 @@ Vuoi davvero svuotare la cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -555,7 +555,7 @@ Imposta il messaggio di sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -563,7 +563,7 @@ per utente apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -623,7 +623,7 @@ Aggiungi apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -651,7 +651,7 @@ Vuoi davvero eliminare questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -753,6 +753,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -795,7 +799,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -835,7 +839,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -1171,7 +1175,7 @@ Bene apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -1239,7 +1243,7 @@ Inserisci il tuo codice del buono: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -1247,7 +1251,7 @@ Impossibile riscattare il codice del buono apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -1263,7 +1267,7 @@ Il codice del buono è stato riscattato apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -1271,7 +1275,7 @@ Ricarica apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -1279,7 +1283,7 @@ Vuoi davvero rimuovere questo metodo di accesso? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -1414,6 +1418,14 @@ 59 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Concedi l’accesso @@ -1490,6 +1502,14 @@ 10 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Valuta @@ -2259,7 +2279,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -2858,6 +2878,18 @@ 190 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Aspetto @@ -3451,7 +3483,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4366,6 +4398,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Obbligazioni @@ -4990,7 +5030,7 @@ 133 - + Export Data Esporta dati @@ -5637,7 +5677,7 @@ Globale apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6025,7 +6065,7 @@ Confermi di voler cancellare questo messaggio di sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6169,7 +6209,7 @@ I dati di mercato sono ritardati di apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6193,7 +6233,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6470,7 +6510,7 @@ Attivo apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6478,7 +6518,7 @@ Chiuso apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6566,7 +6606,7 @@ Confermi di voler chiudere il tuo account Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6582,7 +6622,7 @@ Zona di Pericolo apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6590,7 +6630,7 @@ Chiudi l’account apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6630,7 +6670,7 @@ Ops! C’è stato un errore impostando l’autenticazione biometrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6678,7 +6718,7 @@ Alternativa apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6686,7 +6726,7 @@ App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6754,7 +6794,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6778,7 +6818,7 @@ Investitore apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6790,7 +6830,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6802,7 +6842,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6810,7 +6850,7 @@ Privacy apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6818,7 +6858,7 @@ Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6826,7 +6866,7 @@ Strumento apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6834,7 +6874,7 @@ Esperienza Utente apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6842,7 +6882,7 @@ Ricchezza apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7130,7 +7170,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7508,7 +7548,7 @@ API Key for Ghostfolio Premium Data Provider apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7516,7 +7556,7 @@ Vuoi davvero generare una nuova API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7872,7 +7912,7 @@ Token di sicurezza apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7884,7 +7924,7 @@ Vuoi davvero generare un nuovo token di sicurezza per questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8094,7 +8134,7 @@ L’account utente demo è stato sincronizzato. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.ja.xlf b/apps/client/src/locales/messages.ja.xlf index c3ae93112..0e72d5649 100644 --- a/apps/client/src/locales/messages.ja.xlf +++ b/apps/client/src/locales/messages.ja.xlf @@ -431,6 +431,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency 通貨 @@ -556,7 +564,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1192,7 +1200,7 @@ このクーポンを削除してもよろしいですか? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1200,7 +1208,7 @@ 本当にこのシステムメッセージを削除しますか? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -1208,7 +1216,7 @@ キャッシュをクリアしてもよろしいですか? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1216,7 +1224,7 @@ システムメッセージを設定してください: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1232,7 +1240,7 @@ ユーザーあたり apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1288,7 +1296,7 @@ 追加 apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1456,7 +1464,7 @@ このユーザーを削除してもよろしいですか? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1564,7 +1572,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1640,7 +1648,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1836,7 +1844,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2323,6 +2331,14 @@ 415 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access アクセスを許可する @@ -2352,7 +2368,7 @@ クーポンコードを入力してください。 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -2360,7 +2376,7 @@ クーポンコードを引き換えられませんでした apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2376,7 +2392,7 @@ クーポンコードが引き換えられました apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -2384,7 +2400,7 @@ 再読み込み apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2440,7 +2456,7 @@ このサインイン方法を本当に削除しますか? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -2511,6 +2527,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance 外見 @@ -2619,7 +2647,7 @@ 35 - + Export Data データをエクスポート @@ -2668,7 +2696,7 @@ オッケー apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -3187,6 +3215,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds 債券 @@ -3310,6 +3346,10 @@ Creation 創造 + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3348,7 +3388,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -5129,7 +5169,7 @@ グローバル apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6193,7 +6233,7 @@ 市場データは〜間遅延しています apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6241,7 +6281,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6494,7 +6534,7 @@ クローズ済み apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6502,7 +6542,7 @@ アクティブ apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6590,7 +6630,7 @@ アカウントを閉鎖する apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6598,7 +6638,7 @@ Ghostfolioアカウントを本当に閉鎖しますか? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6614,7 +6654,7 @@ 危険ゾーン apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6654,7 +6694,7 @@ Oops! There was an error setting up biometric authentication. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6702,7 +6742,7 @@ Wealth apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -6762,7 +6802,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6778,7 +6818,7 @@ User Experience apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6786,7 +6826,7 @@ App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6794,7 +6834,7 @@ Tool apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6802,7 +6842,7 @@ Investor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6826,7 +6866,7 @@ Alternative apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6846,7 +6886,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6854,7 +6894,7 @@ Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6874,7 +6914,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6882,7 +6922,7 @@ Privacy apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -7170,7 +7210,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7540,7 +7580,7 @@ Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7548,7 +7588,7 @@ Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7896,7 +7936,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -7904,7 +7944,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -8102,7 +8142,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.ko.xlf b/apps/client/src/locales/messages.ko.xlf index 50b90ef68..b7d7cad22 100644 --- a/apps/client/src/locales/messages.ko.xlf +++ b/apps/client/src/locales/messages.ko.xlf @@ -431,6 +431,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency 통화 @@ -556,7 +564,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1192,7 +1200,7 @@ 이 쿠폰을 정말 삭제하시겠습니까? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1200,7 +1208,7 @@ 이 시스템 메시지를 정말 삭제하시겠습니까? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -1208,7 +1216,7 @@ 정말로 캐시를 플러시하시겠습니까? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1216,7 +1224,7 @@ 시스템 메시지를 설정하십시오: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1232,7 +1240,7 @@ 사용자당 apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1288,7 +1296,7 @@ 추가 apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1456,7 +1464,7 @@ 이 사용자를 정말로 삭제하시겠습니까? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1564,7 +1572,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1640,7 +1648,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1836,7 +1844,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2323,6 +2331,14 @@ 415 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access 액세스 권한 부여 @@ -2352,7 +2368,7 @@ 쿠폰 코드를 입력해주세요. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -2360,7 +2376,7 @@ 쿠폰 코드를 사용할 수 없습니다. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2376,7 +2392,7 @@ 쿠폰 코드가 사용되었습니다. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -2384,7 +2400,7 @@ 새로고침 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2440,7 +2456,7 @@ 이 로그인 방법을 정말로 제거하시겠습니까? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -2511,6 +2527,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance 테마 @@ -2619,7 +2647,7 @@ 35 - + Export Data 데이터 내보내기 @@ -2668,7 +2696,7 @@ 좋아요 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -3187,6 +3215,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds 채권 @@ -3310,6 +3346,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3348,7 +3388,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -5121,7 +5161,7 @@ 글로벌 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6193,7 +6233,7 @@ 시장 데이터가 지연됩니다. apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6241,7 +6281,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6494,7 +6534,7 @@ 닫은 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6502,7 +6542,7 @@ 활동적인 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6590,7 +6630,7 @@ 계정 폐쇄 apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6598,7 +6638,7 @@ 정말로 Ghostfolio 계정을 폐쇄하시겠습니까? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6614,7 +6654,7 @@ 위험지대 apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6654,7 +6694,7 @@ 이런! 생체 인증을 설정하는 중에 오류가 발생했습니다. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6702,7 +6742,7 @@ 재산 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -6762,7 +6802,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6778,7 +6818,7 @@ 사용자 경험 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6786,7 +6826,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6794,7 +6834,7 @@ 도구 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6802,7 +6842,7 @@ 투자자 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6826,7 +6866,7 @@ 대안 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6846,7 +6886,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6854,7 +6894,7 @@ 소프트웨어 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6874,7 +6914,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6882,7 +6922,7 @@ 은둔 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -7170,7 +7210,7 @@ 가 클립보드에 복사되었습니다. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7540,7 +7580,7 @@ 정말로 새 API 키를 생성하시겠습니까? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7548,7 +7588,7 @@ Ghostfolio 프리미엄 데이터 공급자 API 키 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7896,7 +7936,7 @@ 정말로 이 사용자에 대한 새 보안 토큰을 생성하시겠습니까? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -7904,7 +7944,7 @@ 보안 토큰 apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -8102,7 +8142,7 @@ 데모 사용자 계정이 동기화되었습니다. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index d0b61f75f..0f02c4747 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -270,7 +270,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -538,7 +538,7 @@ Wil je deze coupon echt verwijderen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -546,7 +546,7 @@ Wil je echt de cache legen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -554,7 +554,7 @@ Stel je systeemboodschap in: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -562,7 +562,7 @@ per gebruiker apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -622,7 +622,7 @@ Toevoegen apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -650,7 +650,7 @@ Wilt je deze gebruiker echt verwijderen? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -752,6 +752,10 @@ Creation Aanmaakdatum + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -794,7 +798,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -834,7 +838,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -1170,7 +1174,7 @@ Oké apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -1238,7 +1242,7 @@ Voer je couponcode in: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -1246,7 +1250,7 @@ Kon je kortingscode niet inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -1262,7 +1266,7 @@ Je couponcode is ingewisseld apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -1270,7 +1274,7 @@ Herladen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -1278,7 +1282,7 @@ Wil je deze aanmeldingsmethode echt verwijderen? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -1413,6 +1417,14 @@ 59 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Toegang verlenen @@ -1489,6 +1501,14 @@ 10 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Valuta @@ -2258,7 +2278,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -2857,6 +2877,18 @@ 190 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Weergave @@ -3450,7 +3482,7 @@ zojuist apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4365,6 +4397,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Obligaties @@ -4989,7 +5029,7 @@ 133 - + Export Data Exporteer Data @@ -5636,7 +5676,7 @@ Wereldwijd apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6024,7 +6064,7 @@ Wilt u dit systeembericht echt verwijderen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6168,7 +6208,7 @@ Markt data is vertraagd voor apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6192,7 +6232,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6469,7 +6509,7 @@ Actief apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6477,7 +6517,7 @@ Gesloten apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6565,7 +6605,7 @@ Wilt u uw Ghostfolio account echt sluiten? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6581,7 +6621,7 @@ Gevarenzone apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6589,7 +6629,7 @@ Account Sluiten apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6629,7 +6669,7 @@ Oeps! Er is een fout opgetreden met het instellen van de biometrische authenticatie. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6677,7 +6717,7 @@ Alternatief apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6685,7 +6725,7 @@ App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6753,7 +6793,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6777,7 +6817,7 @@ Investeerder apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6789,7 +6829,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6801,7 +6841,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6809,7 +6849,7 @@ Privacy apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6817,7 +6857,7 @@ Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6825,7 +6865,7 @@ Hulpmiddel apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6833,7 +6873,7 @@ Gebruikers Ervaring apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6841,7 +6881,7 @@ Vermogen apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7129,7 +7169,7 @@ is naar het klembord gekopieerd apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7507,7 +7547,7 @@ Ghostfolio Premium Gegevensleverancier API-sleutel apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7515,7 +7555,7 @@ Wilt u echt een nieuwe API-sleutel genereren? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7871,7 +7911,7 @@ Beveiligingstoken apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Wilt u echt een nieuw beveiligingstoken voor deze gebruiker aanmaken? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Demo-gebruikersaccount is gesynchroniseerd. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index cc1137028..2c993890c 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -422,6 +422,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Waluta @@ -547,7 +555,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1159,7 +1167,7 @@ Czy naprawdę chcesz usunąć ten kupon? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1167,7 +1175,7 @@ Czy naprawdę chcesz usunąć tę wiadomość systemową? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -1175,7 +1183,7 @@ Czy naprawdę chcesz wyczyścić pamięć podręczną? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1183,7 +1191,7 @@ Proszę ustawić swoją wiadomość systemową: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1199,7 +1207,7 @@ na Użytkownika apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1255,7 +1263,7 @@ Dodaj apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1423,7 +1431,7 @@ Czy na pewno chcesz usunąć tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1531,7 +1539,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1607,7 +1615,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1803,7 +1811,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2290,6 +2298,14 @@ 415 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Przyznaj dostęp @@ -2319,7 +2335,7 @@ Wpisz kod kuponu: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -2327,7 +2343,7 @@ Nie udało się zrealizować kodu kuponu apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2343,7 +2359,7 @@ Kupon został zrealizowany apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -2351,7 +2367,7 @@ Odśwież apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2407,7 +2423,7 @@ Czy na pewno chcesz usunąć tą metode logowania? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -2478,6 +2494,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Wygląd (tryb) @@ -2586,7 +2614,7 @@ 35 - + Export Data Eksportuj Dane @@ -2635,7 +2663,7 @@ Okej apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -3154,6 +3182,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Obligacje @@ -3277,6 +3313,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3315,7 +3355,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -5076,7 +5116,7 @@ Globalny apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6168,7 +6208,7 @@ Dane rynkowe są opóźnione o apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6192,7 +6232,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6469,7 +6509,7 @@ Antywne apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6477,7 +6517,7 @@ Zamknięte apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6565,7 +6605,7 @@ Czy na pewno chcesz zamknąć swoje konto Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6581,7 +6621,7 @@ Strefa Zagrożenia apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6589,7 +6629,7 @@ Zamknij Konto apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6629,7 +6669,7 @@ Ups! Wystąpił błąd podczas konfigurowania uwierzytelniania biometrycznego. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6677,7 +6717,7 @@ Alternatywa apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6685,7 +6725,7 @@ Aplikacja apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6753,7 +6793,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6777,7 +6817,7 @@ Inwestor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6789,7 +6829,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6801,7 +6841,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6809,7 +6849,7 @@ Prywatność apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6817,7 +6857,7 @@ Oprogramowanie apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6825,7 +6865,7 @@ Narzędzie apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6833,7 +6873,7 @@ Doświadczenie Użytkownika apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6841,7 +6881,7 @@ Majątek apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7129,7 +7169,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7507,7 +7547,7 @@ Klucz API dostawcy danych Premium Ghostfolio apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7515,7 +7555,7 @@ Czy na pewno chcesz wygenerować nowy klucz API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7871,7 +7911,7 @@ Token bezpieczeństwa apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Czy napewno chcesz wygenerować nowy token bezpieczeństwa dla tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Konto użytkownika demonstracyjnego zostało zsynchronizowane. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 870e1f43b..af9f30ed2 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -185,6 +185,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Moeda @@ -326,7 +334,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -646,7 +654,7 @@ Deseja realmente eliminar este cupão? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -654,7 +662,7 @@ Deseja realmente limpar a cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -662,7 +670,7 @@ Por favor, defina a sua mensagem do sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -670,7 +678,7 @@ por Utilizador apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -718,7 +726,7 @@ Adicionar apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -746,7 +754,7 @@ Deseja realmente excluir este utilizador? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -830,7 +838,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -906,7 +914,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1018,7 +1026,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -1466,7 +1474,7 @@ OK apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -1546,7 +1554,7 @@ Por favor, insira o seu código de cupão: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -1554,7 +1562,7 @@ Não foi possível resgatar o código de cupão apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -1570,7 +1578,7 @@ Código de cupão foi resgatado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -1578,7 +1586,7 @@ Atualizar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -1586,7 +1594,7 @@ Deseja realmente remover este método de início de sessão? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -1709,6 +1717,18 @@ 246 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Aparência @@ -1781,6 +1801,14 @@ 59 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Conceder Acesso @@ -2700,6 +2728,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3350,7 +3382,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4365,6 +4397,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Obrigações @@ -4989,7 +5029,7 @@ 133 - + Export Data Exportar dados @@ -5636,7 +5676,7 @@ Global apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6024,7 +6064,7 @@ Você realmente deseja excluir esta mensagem do sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6168,7 +6208,7 @@ Dados de mercado estão atrasados para apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6192,7 +6232,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6469,7 +6509,7 @@ Ativo apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6477,7 +6517,7 @@ Fechado apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6565,7 +6605,7 @@ Você realmente deseja encerrar sua conta Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6581,7 +6621,7 @@ Zona de perigo apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6589,7 +6629,7 @@ Fechar conta apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6629,7 +6669,7 @@ Ops! Ocorreu um erro ao configurar a autenticação biométrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6677,7 +6717,7 @@ Alternativo apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6685,7 +6725,7 @@ Aplicativo apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6753,7 +6793,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6777,7 +6817,7 @@ Investidor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6789,7 +6829,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6801,7 +6841,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6809,7 +6849,7 @@ Privacidade apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6817,7 +6857,7 @@ Programas apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6825,7 +6865,7 @@ Ferramenta apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6833,7 +6873,7 @@ Experiência do usuário apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6841,7 +6881,7 @@ Fortuna apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7129,7 +7169,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7507,7 +7547,7 @@ Chave de API do Provedor de Dados do Ghostfolio Premium apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7515,7 +7555,7 @@ Você realmente deseja gerar uma nova chave de API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7871,7 +7911,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 27559e50d..8514610e5 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -382,6 +382,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Para Birimi @@ -507,7 +515,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1055,7 +1063,7 @@ Bu kuponu gerçekten silmek istiyor musunuz? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1063,7 +1071,7 @@ Önbelleği temizlemeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1071,7 +1079,7 @@ Lütfen sistem mesajınızı belirleyin: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1079,7 +1087,7 @@ Kullanıcı başına apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1151,7 +1159,7 @@ Ekle apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1271,7 +1279,7 @@ Bu kullanıcıyı silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1379,7 +1387,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1455,7 +1463,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1651,7 +1659,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2179,7 +2187,7 @@ Tamam apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -2666,6 +2674,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Tahviller @@ -2801,6 +2817,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -2839,7 +2859,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4500,7 +4520,7 @@ Küresel apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -4531,6 +4551,14 @@ 332 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Erişim izni ver @@ -4576,7 +4604,7 @@ Lütfen kupon kodunuzu girin: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -4584,7 +4612,7 @@ Kupon kodu kullanılamadı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -4600,7 +4628,7 @@ Kupon kodu kullanıldı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -4608,7 +4636,7 @@ Yeniden Yükle apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -4616,7 +4644,7 @@ Bu giriş yöntemini kaldırmayı gerçekten istiyor musunuz? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -4735,6 +4763,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Görünüm @@ -4831,7 +4871,7 @@ 35 - + Export Data Verileri Dışa Aktar @@ -6024,7 +6064,7 @@ Bu sistem mesajını silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -6168,7 +6208,7 @@ Piyasa verileri gecikmeli apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6192,7 +6232,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6469,7 +6509,7 @@ Aktif apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6477,7 +6517,7 @@ Kapalı apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6565,7 +6605,7 @@ Ghostfolio hesabınızı kapatmak istediğinize emin misiniz? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6581,7 +6621,7 @@ Tehlikeli Alan apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6589,7 +6629,7 @@ Hesabı Kapat apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6629,7 +6669,7 @@ Oops! Biyometrik kimlik doğrulama ayarlanırken bir hata oluştu. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6677,7 +6717,7 @@ Alternatif apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6685,7 +6725,7 @@ App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6753,7 +6793,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6777,7 +6817,7 @@ Yatırımcı apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6789,7 +6829,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6801,7 +6841,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6809,7 +6849,7 @@ Gizlilik apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6817,7 +6857,7 @@ Yazılım apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6825,7 +6865,7 @@ Araç apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6833,7 +6873,7 @@ Kullanıcı Deneyimi apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6841,7 +6881,7 @@ Zenginlik apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7129,7 +7169,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7507,7 +7547,7 @@ Ghostfolio Premium Veri Sağlayıcı API Anahtarı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7515,7 +7555,7 @@ Yeni bir API anahtarı oluşturmak istediğinize emin misiniz? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7871,7 +7911,7 @@ Güvenlik belirteci apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Bu kullanıcı için yeni bir güvenlik belirteci oluşturmak istediğinize emin misiniz? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Demo kullanıcı hesabı senkronize edildi. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 18eb0ae03..8152c9936 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -522,6 +522,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency Валюта @@ -647,7 +655,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1327,7 +1335,7 @@ Ви дійсно хочете видалити цей купон? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1335,7 +1343,7 @@ Ви дійсно хочете видалити це системне повідомлення? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -1343,7 +1351,7 @@ Ви дійсно хочете очистити кеш? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1351,7 +1359,7 @@ Будь ласка, встановіть ваше системне повідомлення: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1367,7 +1375,7 @@ на користувача apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1435,7 +1443,7 @@ Додати apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1719,7 +1727,7 @@ Ви дійсно хочете видалити цього користувача? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1815,7 +1823,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1911,7 +1919,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -2015,7 +2023,7 @@ Активний apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -2023,7 +2031,7 @@ Закритий apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -2235,7 +2243,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2283,7 +2291,7 @@ Ринкові дані затримуються для apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -2754,6 +2762,14 @@ 204 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access Надати доступ @@ -2843,7 +2859,7 @@ ОК apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -2867,7 +2883,7 @@ Ключ API Ghostfolio Premium Data Provider apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -2875,7 +2891,7 @@ Ви дійсно хочете згенерувати новий ключ API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -2883,7 +2899,7 @@ Не вдалося обміняти код купона apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2899,7 +2915,7 @@ Код купона був обміняний apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -2907,7 +2923,7 @@ Перезавантажити apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2915,7 +2931,7 @@ Будь ласка, введіть ваш код купона. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -2971,7 +2987,7 @@ Ви дійсно хочете закрити ваш обліковий запис Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -2987,7 +3003,7 @@ Ви дійсно хочете вилучити цей спосіб входу? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -3003,7 +3019,7 @@ Упс! Виникла помилка під час налаштування біометричної автентифікації. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -3082,6 +3098,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance Зовнішній вигляд @@ -3174,7 +3202,7 @@ 255 - + Export Data Експортувати дані @@ -3187,7 +3215,7 @@ Зона небезпеки apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -3195,7 +3223,7 @@ Закрити обліковий запис apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -3811,6 +3839,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds Облігації @@ -3934,6 +3970,10 @@ Creation Створення + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3972,7 +4012,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -5112,7 +5152,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -5532,7 +5572,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -5739,7 +5779,7 @@ Глобальний apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -5751,7 +5791,7 @@ Альтернатива apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -5759,7 +5799,7 @@ Додаток apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -5827,7 +5867,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -5851,7 +5891,7 @@ Інвестор apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -5863,7 +5903,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -5875,7 +5915,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -5883,7 +5923,7 @@ Конфіденційність apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -5891,7 +5931,7 @@ Програмне забезпечення apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -5899,7 +5939,7 @@ Інструмент apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -5907,7 +5947,7 @@ Користувацький досвід apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -5915,7 +5955,7 @@ Багатство apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7871,7 +7911,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7883,7 +7923,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8093,7 +8133,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 672554a58..bf7304782 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -404,6 +404,13 @@ 62 + + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency @@ -525,7 +532,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1103,28 +1110,28 @@ Do you really want to delete this coupon? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 Do you really want to delete this system message? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 Do you really want to flush the cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 Please set your system message: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1138,7 +1145,7 @@ per User apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1187,7 +1194,7 @@ Add apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1337,7 +1344,7 @@ Do you really want to delete this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1435,7 +1442,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1506,7 +1513,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1682,7 +1689,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2130,6 +2137,13 @@ 415 + + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access @@ -2155,14 +2169,14 @@ Please enter your coupon code. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 Could not redeem coupon code apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2176,14 +2190,14 @@ Coupon code has been redeemed apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 Reload apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2234,7 +2248,7 @@ Do you really want to remove this sign in method? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -2297,6 +2311,17 @@ 153 + + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance @@ -2393,7 +2418,7 @@ 35 - + Export Data apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2437,7 +2462,7 @@ Okay apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -2925,6 +2950,13 @@ 25 + + For security reasons, please delete all activities and accounts first before your Ghostfolio account can be closed. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds @@ -3035,6 +3067,10 @@ Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3071,7 +3107,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -4671,7 +4707,7 @@ Global apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -5632,7 +5668,7 @@ Market data is delayed for apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -5676,7 +5712,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -5903,14 +5939,14 @@ Closed apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 Active apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -5987,14 +6023,14 @@ Close Account apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 Do you really want to close your Ghostfolio account? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6008,7 +6044,7 @@ Danger Zone apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6043,7 +6079,7 @@ Oops! There was an error setting up biometric authentication. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6085,7 +6121,7 @@ Wealth apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -6144,7 +6180,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6158,28 +6194,28 @@ User Experience apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 App apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 Tool apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 Investor apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6200,7 +6236,7 @@ Alternative apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6218,14 +6254,14 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 Software apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6243,14 +6279,14 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 Privacy apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6513,7 +6549,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -6845,14 +6881,14 @@ Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7165,14 +7201,14 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7349,7 +7385,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 1537fa141..2230a6f86 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -431,6 +431,14 @@ 62 + + Copy + Copy + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html + 20 + + Currency 货币 @@ -556,7 +564,7 @@ apps/client/src/app/components/admin-overview/admin-overview.html - 213 + 235 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1168,7 +1176,7 @@ 您确实要删除此优惠券吗? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 226 + 232 @@ -1176,7 +1184,7 @@ 您真的要删除这条系统消息吗? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 239 + 245 @@ -1184,7 +1192,7 @@ 您真的要刷新缓存吗? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 263 + 269 @@ -1192,7 +1200,7 @@ 请设置您的系统消息: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 283 + 289 @@ -1208,7 +1216,7 @@ 每位用户 apps/client/src/app/components/admin-overview/admin-overview.component.ts - 170 + 175 @@ -1264,7 +1272,7 @@ 添加 apps/client/src/app/components/admin-overview/admin-overview.html - 265 + 287 libs/ui/src/lib/account-balances/account-balances.component.html @@ -1432,7 +1440,7 @@ 您真的要删除该用户吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 238 + 244 @@ -1540,7 +1548,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 117 + 123 libs/common/src/lib/routes/routes.ts @@ -1616,7 +1624,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 194 + 203 @@ -1812,7 +1820,7 @@ apps/client/src/app/components/user-account-settings/user-account-settings.html - 303 + 306 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -2299,6 +2307,14 @@ 415 + + The value has been copied to the clipboard + The value has been copied to the clipboard + + libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts + 46 + + Grant access 授予访问权限 @@ -2328,7 +2344,7 @@ 请输入您的优惠券代码。 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 210 + 208 @@ -2336,7 +2352,7 @@ 无法兑换优惠券代码 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 174 + 172 @@ -2352,7 +2368,7 @@ 优惠券代码已被兑换 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 187 + 185 @@ -2360,7 +2376,7 @@ 重新加载 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 188 + 186 @@ -2416,7 +2432,7 @@ 您确实要删除此登录方法吗? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 291 @@ -2487,6 +2503,18 @@ 153 + + Close Account + Close Account + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 337 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 345 + + Appearance 外观 @@ -2595,7 +2623,7 @@ 35 - + Export Data 导出数据 @@ -2644,7 +2672,7 @@ 好的 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 149 + 150 apps/client/src/app/core/http-response.interceptor.ts @@ -3163,6 +3191,14 @@ 25 + + 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. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 348 + + Bonds 债券 @@ -3286,6 +3322,10 @@ Creation Creation + + apps/client/src/app/components/admin-overview/admin-overview.html + 185 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 145 @@ -3324,7 +3364,7 @@ just now apps/client/src/app/components/admin-users/admin-users.component.ts - 211 + 217 @@ -5105,7 +5145,7 @@ 全球的 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 54 + 59 libs/ui/src/lib/i18n.ts @@ -6169,7 +6209,7 @@ 市场数据延迟 apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts - 92 + 103 @@ -6217,7 +6257,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 112 + 118 @@ -6470,7 +6510,7 @@ 已关闭 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 62 + 64 @@ -6478,7 +6518,7 @@ 活跃 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 61 + 63 @@ -6566,7 +6606,7 @@ 您确定要关闭您的 Ghostfolio 账户吗? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 217 @@ -6582,7 +6622,7 @@ 危险区域 apps/client/src/app/components/user-account-settings/user-account-settings.html - 296 + 293 @@ -6590,7 +6630,7 @@ 关闭账户 apps/client/src/app/components/user-account-settings/user-account-settings.html - 333 + 353 @@ -6630,7 +6670,7 @@ 哎呀!设置生物识别认证时发生错误。 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 331 + 340 @@ -6678,7 +6718,7 @@ 另类 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 108 + 114 @@ -6686,7 +6726,7 @@ 应用 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 109 + 115 @@ -6754,7 +6794,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 110 + 116 @@ -6778,7 +6818,7 @@ 投资者 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 113 + 119 @@ -6790,7 +6830,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 114 + 120 @@ -6802,7 +6842,7 @@ apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 116 + 122 @@ -6810,7 +6850,7 @@ 隐私 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 118 + 124 @@ -6818,7 +6858,7 @@ 软件 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 119 + 125 @@ -6826,7 +6866,7 @@ 工具 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 120 + 126 @@ -6834,7 +6874,7 @@ 用户体验 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 121 + 127 @@ -6842,7 +6882,7 @@ 财富 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts - 122 + 128 @@ -7130,7 +7170,7 @@ has been copied to the clipboard apps/client/src/app/components/admin-overview/admin-overview.component.ts - 382 + 388 libs/ui/src/lib/value/value.component.ts @@ -7508,7 +7548,7 @@ Ghostfolio Premium 数据提供者 API 密钥 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 152 @@ -7516,7 +7556,7 @@ 您确定要生成新的 API 密钥吗? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 157 @@ -7872,7 +7912,7 @@ 安全令牌 apps/client/src/app/components/admin-users/admin-users.component.ts - 258 + 264 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7884,7 +7924,7 @@ 您确定要为此用户生成新的安全令牌吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 263 + 269 @@ -8094,7 +8134,7 @@ 演示用户账户已同步。 apps/client/src/app/components/admin-overview/admin-overview.component.ts - 307 + 313 From 063b6464da09185106ea3a865c69db2fb5230a7d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:06:09 +0200 Subject: [PATCH 16/71] Task/improve language localization for DE (20260708) (#7276) Update translations --- apps/client/src/locales/messages.de.xlf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 1d7d6b1b1..313a22ce3 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -1435,7 +1435,7 @@ The value has been copied to the clipboard - The value has been copied to the clipboard + Der Wert wurde in die Zwischenablage kopiert libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts 46 @@ -1519,7 +1519,7 @@ Copy - Copy + Kopieren libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html 20 @@ -2895,7 +2895,7 @@ Close Account - Close Account + Konto schliessen apps/client/src/app/components/user-account-settings/user-account-settings.html 337 @@ -4423,7 +4423,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. + Bitte lösche aus Sicherheitsgründen zuerst alle Aktivitäten und Konten, bevor dein Ghostfolio Konto geschlossen werden kann. apps/client/src/app/components/user-account-settings/user-account-settings.html 348 From 54103d5bebf9595690d8ed1674c1fba83073da24 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 8 Jul 2026 12:06:56 +0200 Subject: [PATCH 17/71] Feature/add Karpathy guidelines skills (#7273) Add Karpathy guidelines skills --- .agents/skills/karpathy-guidelines/SKILL.md | 72 +++++++++++++++++++++ .claude/skills/karpathy-guidelines | 1 + skills-lock.json | 6 ++ 3 files changed, 79 insertions(+) create mode 100644 .agents/skills/karpathy-guidelines/SKILL.md create mode 120000 .claude/skills/karpathy-guidelines diff --git a/.agents/skills/karpathy-guidelines/SKILL.md b/.agents/skills/karpathy-guidelines/SKILL.md new file mode 100644 index 000000000..029e4d55d --- /dev/null +++ b/.agents/skills/karpathy-guidelines/SKILL.md @@ -0,0 +1,72 @@ +--- +name: karpathy-guidelines +description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria. +license: MIT +--- + +# Karpathy Guidelines + +Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: + +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: + +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: + +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: + +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: + +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. diff --git a/.claude/skills/karpathy-guidelines b/.claude/skills/karpathy-guidelines new file mode 120000 index 000000000..743bef527 --- /dev/null +++ b/.claude/skills/karpathy-guidelines @@ -0,0 +1 @@ +../../.agents/skills/karpathy-guidelines \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json index 0ecf51f96..06bf64189 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -7,6 +7,12 @@ "skillPath": "angular-developer/SKILL.md", "computedHash": "28eb592b92e5a24c4e3a1c0229a854069f0b8c49bed7b8d2bf6b852812dbe214" }, + "karpathy-guidelines": { + "source": "multica-ai/andrej-karpathy-skills", + "sourceType": "github", + "skillPath": "skills/karpathy-guidelines/SKILL.md", + "computedHash": "41e8ca055bbde13d240776a14a076a59614057200340c243130a76ba4e64cac8" + }, "nestjs-best-practices": { "source": "kadajett/agent-nestjs-skills", "sourceType": "github", From 9b590efa873c7da017c53d68f69c698580c398fb Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:33:46 +0200 Subject: [PATCH 18/71] Task/rename SymbolProfileOverrides to AssetProfileOverrides (#7271) * Rename SymbolProfileOverrides to AssetProfileOverrides * Update changelog --- CHANGELOG.md | 1 + .../src/app/activities/activities.service.ts | 6 +-- apps/api/src/app/admin/admin.service.ts | 4 +- .../asset-profiles/asset-profiles.service.ts | 6 +-- .../symbol-profile/symbol-profile.service.ts | 24 +++++----- libs/common/src/lib/helper.ts | 6 +-- prisma/schema.prisma | 46 ++++++++++--------- 7 files changed, 48 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6137890..3f2fc105d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set the change detection strategy to `OnPush` in the portfolio holdings 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 +- Renamed the `SymbolProfileOverrides` _Prisma_ data model to `AssetProfileOverrides` while keeping the database table name - 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/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index b404b6a37..bd08d05f9 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -615,14 +615,14 @@ export class ActivitiesService { }, { OR: [ - { SymbolProfileOverrides: { is: null } }, - { SymbolProfileOverrides: { assetClass: null } } + { assetProfileOverrides: { is: null } }, + { assetProfileOverrides: { assetClass: null } } ] } ] }, { - SymbolProfileOverrides: { + assetProfileOverrides: { OR: filtersByAssetClass.map(({ id }) => { return { assetClass: AssetClass[id] }; }) diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index e6f5fd700..81a48aff2 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -299,7 +299,7 @@ export class AdminService { ); } } else { - const symbolProfileOverrides = { + const assetProfileOverrides = { assetClass: assetClass as AssetClass, assetSubClass: assetSubClass as AssetSubClass, countries: countries as Prisma.JsonArray, @@ -320,7 +320,7 @@ export class AdminService { symbolMapping, ...this.symbolProfileService.getAssetProfileUpdateInput( { dataSource, symbol }, - symbolProfileOverrides + assetProfileOverrides ) }; diff --git a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts index 68b8d5627..b1a36df26 100644 --- a/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts +++ b/apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts @@ -198,6 +198,7 @@ export class AssetProfilesService { take: 1 }, assetClass: true, + assetProfileOverrides: true, assetSubClass: true, comment: true, countries: true, @@ -210,8 +211,7 @@ export class AssetProfilesService { name: true, scraperConfiguration: true, sectors: true, - symbol: true, - SymbolProfileOverrides: true + symbol: true } }), this.prismaService.symbolProfile.count({ where }) @@ -268,7 +268,7 @@ export class AssetProfilesService { const { assetClass, assetSubClass, countries, name, sectors } = applyAssetProfileOverrides( assetProfile, - assetProfile.SymbolProfileOverrides + assetProfile.assetProfileOverrides ); const countriesCount = countries ? Object.keys(countries).length : 0; diff --git a/apps/api/src/services/symbol-profile/symbol-profile.service.ts b/apps/api/src/services/symbol-profile/symbol-profile.service.ts index 5d0968c5c..7157f0856 100644 --- a/apps/api/src/services/symbol-profile/symbol-profile.service.ts +++ b/apps/api/src/services/symbol-profile/symbol-profile.service.ts @@ -12,10 +12,10 @@ import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; import { Injectable } from '@nestjs/common'; import { + AssetProfileOverrides, DataSource, Prisma, - SymbolProfile, - SymbolProfileOverrides + SymbolProfile } from '@prisma/client'; import { continents, countries } from 'countries-list'; @@ -85,12 +85,12 @@ export class SymbolProfileService { } return { - SymbolProfileOverrides: { + assetProfileOverrides: { upsert: { create: - data as Prisma.SymbolProfileOverridesCreateWithoutSymbolProfileInput, + data as Prisma.AssetProfileOverridesCreateWithoutSymbolProfileInput, update: - data as Prisma.SymbolProfileOverridesUpdateWithoutSymbolProfileInput + data as Prisma.AssetProfileOverridesUpdateWithoutSymbolProfileInput } } }; @@ -112,7 +112,7 @@ export class SymbolProfileService { select: { date: true }, take: 1 }, - SymbolProfileOverrides: true + assetProfileOverrides: true }, where: { OR: aAssetProfileIdentifiers.map(({ dataSource, symbol }) => { @@ -137,7 +137,7 @@ export class SymbolProfileService { _count: { select: { activities: true, watchedBy: true } }, - SymbolProfileOverrides: true + assetProfileOverrides: true }, where: { id: { @@ -174,6 +174,7 @@ export class SymbolProfileService { { dataSource, symbol }: AssetProfileIdentifier, { assetClass, + assetProfileOverrides, assetSubClass, comment, countries, @@ -185,13 +186,13 @@ export class SymbolProfileService { scraperConfiguration, sectors, symbolMapping, - SymbolProfileOverrides, url }: Prisma.SymbolProfileUpdateInput ) { return this.prismaService.symbolProfile.update({ data: { assetClass, + assetProfileOverrides, assetSubClass, comment, countries, @@ -203,7 +204,6 @@ export class SymbolProfileService { scraperConfiguration, sectors, symbolMapping, - SymbolProfileOverrides, url }, where: { dataSource_symbol: { dataSource, symbol } } @@ -216,13 +216,13 @@ export class SymbolProfileService { activities?: { date: Date; }[]; - SymbolProfileOverrides: SymbolProfileOverrides; + assetProfileOverrides: AssetProfileOverrides; })[] ): EnhancedSymbolProfile[] { return symbolProfiles.map((symbolProfile) => { const symbolProfileWithOverrides = applyAssetProfileOverrides( symbolProfile, - symbolProfile.SymbolProfileOverrides + symbolProfile.assetProfileOverrides ); const item = { @@ -252,7 +252,7 @@ export class SymbolProfileService { item.dateOfFirstActivity = symbolProfile.activities?.[0]?.date; delete item.activities; - delete item.SymbolProfileOverrides; + delete item.assetProfileOverrides; return item; }); diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index f9dcd52cf..42aec19ac 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -1,10 +1,10 @@ import { NumberParser } from '@internationalized/number'; import { Type as ActivityType, + AssetProfileOverrides, MarketData, Prisma, - SymbolProfile, - SymbolProfileOverrides + SymbolProfile } from '@prisma/client'; import { Big } from 'big.js'; import { isISO4217CurrencyCode } from 'class-validator'; @@ -56,7 +56,7 @@ export const DATE_FORMAT_YEARLY = 'yyyy'; export function applyAssetProfileOverrides>( assetProfile: T, - assetProfileOverrides: SymbolProfileOverrides | null + assetProfileOverrides: AssetProfileOverrides | null ): T { if (!assetProfileOverrides) { return assetProfile; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ed07d6f47..733399506 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -90,6 +90,21 @@ model ApiKey { @@index([userId]) } +model AssetProfileOverrides { + assetClass AssetClass? + assetSubClass AssetSubClass? + countries Json? @default("[]") + holdings Json? @default("[]") + name String? + sectors Json? @default("[]") + symbolProfile SymbolProfile @relation(fields: [symbolProfileId], onDelete: Cascade, references: [id]) + symbolProfileId String @id + updatedAt DateTime @updatedAt + url String? + + @@map("SymbolProfileOverrides") +} + model AssetProfileResolution { createdAt DateTime @default(now()) currency String @@ -187,32 +202,32 @@ model Settings { model SymbolProfile { activities Order[] assetClass AssetClass? + assetProfileOverrides AssetProfileOverrides? assetSubClass AssetSubClass? comment String? countries Json? - createdAt DateTime @default(now()) + createdAt DateTime @default(now()) currency String cusip String? - dataGatheringFrequency DataGatheringFrequency @default(DAILY) + dataGatheringFrequency DataGatheringFrequency @default(DAILY) dataSource DataSource figi String? figiComposite String? figiShareClass String? - holdings Json? @default("[]") - id String @id @default(uuid()) - isActive Boolean @default(true) + holdings Json? @default("[]") + id String @id @default(uuid()) + isActive Boolean @default(true) isin String? name String? scraperConfiguration Json? sectors Json? symbol String symbolMapping Json? - updatedAt DateTime @updatedAt + updatedAt DateTime @updatedAt url String? - user User? @relation(fields: [userId], onDelete: Cascade, references: [id]) + user User? @relation(fields: [userId], onDelete: Cascade, references: [id]) userId String? - watchedBy User[] @relation("UserWatchlist") - SymbolProfileOverrides SymbolProfileOverrides? + watchedBy User[] @relation("UserWatchlist") @@unique([dataSource, symbol]) @@index([assetClass]) @@ -226,19 +241,6 @@ model SymbolProfile { @@index([symbol]) } -model SymbolProfileOverrides { - assetClass AssetClass? - assetSubClass AssetSubClass? - countries Json? @default("[]") - holdings Json? @default("[]") - name String? - sectors Json? @default("[]") - symbolProfileId String @id - updatedAt DateTime @updatedAt - url String? - SymbolProfile SymbolProfile @relation(fields: [symbolProfileId], onDelete: Cascade, references: [id]) -} - model Subscription { createdAt DateTime @default(now()) expiresAt DateTime From 73d002f5c0131367470310c9dcb878916b1ff1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D1=8F=D1=87=D0=B5=D1=81=D0=BB=D0=B0=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D1=88=D0=B5=D0=BD=D0=BA=D0=BE?= <44668361+SlavaDoroshenko@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:52:46 +0300 Subject: [PATCH 19/71] Task/set change detection strategy to OnPush in allocations page (#7274) * Set the change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 1 + .../pages/portfolio/allocations/allocations-page.component.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f2fc105d..0b0237916 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 - Improved the user account deletion flow in the user settings of the user account page +- Set the change detection strategy to `OnPush` in the allocations page - Set the change detection strategy to `OnPush` in the portfolio holdings 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 diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 1ed8bfa66..f1dfed942 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -22,6 +22,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -48,6 +49,7 @@ import { filter, switchMap, tap } from 'rxjs'; import { AllocationsPageParams } from './interfaces/interfaces'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfPortfolioProportionChartComponent, GfPremiumIndicatorComponent, @@ -161,6 +163,8 @@ export class GfAllocationsPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + + this.changeDetectorRef.markForCheck(); }); this.userService.stateChanged From d44243d737a3164311d04fbf5439c3dda72228d1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:05:02 +0200 Subject: [PATCH 20/71] Task/improve date formatting in market data table of admin control panel (#7278) * Reuse value component for first activity column * Update changelog --- CHANGELOG.md | 1 + .../admin-market-data/admin-market-data.component.ts | 10 +--------- .../admin-market-data/admin-market-data.html | 8 +++++++- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0237916..88b32f032 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 - Improved the user account deletion flow in the user settings of the user account page +- Improved the date formatting of the first activity in the historical market data table of the admin control panel - Set the change detection strategy to `OnPush` in the allocations page - Set the change detection strategy to `OnPush` in the portfolio holdings page - Set the change detection strategy to `OnPush` in the _FIRE_ page diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index ae0f8fda2..c64405cb6 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -4,10 +4,7 @@ import { DEFAULT_LOCALE, DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; -import { - canDeleteAssetProfile, - getDateFormatString -} from '@ghostfolio/common/helper'; +import { canDeleteAssetProfile } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, AssetProfileItem, @@ -153,7 +150,6 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { ]; protected readonly canDeleteAssetProfile = canDeleteAssetProfile; protected dataSource = new MatTableDataSource(); - protected defaultDateFormat: string; protected readonly displayedColumns: string[] = []; protected readonly filters$ = new Subject(); protected isLoading = true; @@ -236,10 +232,6 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { .subscribe((state) => { if (state?.user) { this.user = state.user; - - this.defaultDateFormat = getDateFormatString( - this.user.settings.locale - ); } }); diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html index 83703b548..ae7adfdd8 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.html +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -147,7 +147,13 @@ First Activity - {{ (element.date | date: defaultDateFormat) ?? '' }} + @if (element.date) { + + } From b0747b025cca4b93231c6b17db2ff74161171c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D1=8F=D1=87=D0=B5=D1=81=D0=BB=D0=B0=D0=B2=20=D0=94?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D1=88=D0=B5=D0=BD=D0=BA=D0=BE?= <44668361+SlavaDoroshenko@users.noreply.github.com> Date: Wed, 8 Jul 2026 22:12:54 +0300 Subject: [PATCH 21/71] Task/set change detection strategy to OnPush in analysis page (#7275) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 1 + .../pages/portfolio/analysis/analysis-page.component.ts | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b32f032..22406b5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the user account deletion flow in the user settings of the user account page - Improved the date formatting of the first activity in the historical market data table of the admin control panel - Set the change detection strategy to `OnPush` in the allocations page +- Set the change detection strategy to `OnPush` in the analysis page - Set the change detection strategy to `OnPush` in the portfolio holdings 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 diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 6c49a9030..ae41f54c8 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -25,6 +25,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { Clipboard } from '@angular/cdk/clipboard'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -51,6 +52,7 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfBenchmarkComparatorComponent, GfInvestmentChartComponent, @@ -148,6 +150,8 @@ export class GfAnalysisPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + + this.changeDetectorRef.markForCheck(); }); this.userService.stateChanged @@ -167,6 +171,8 @@ export class GfAnalysisPageComponent implements OnInit { this.update(); } + + this.changeDetectorRef.markForCheck(); }); } @@ -229,6 +235,8 @@ export class GfAnalysisPageComponent implements OnInit { } else if (mode === 'portfolio') { this.isLoadingPortfolioPrompt = false; } + + this.changeDetectorRef.markForCheck(); }); } @@ -382,6 +390,7 @@ export class GfAnalysisPageComponent implements OnInit { }); this.fetchDividendsAndInvestments(); + this.changeDetectorRef.markForCheck(); } From 712bd01c736575a8bb2b7d9bedd5a13b68322270 Mon Sep 17 00:00:00 2001 From: Arjun jaiswal <156437180+Arjun8242@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:55:33 +0530 Subject: [PATCH 22/71] Task/set change detection strategy to OnPush in activities page (#7248) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 2 ++ .../pages/portfolio/activities/activities-page.component.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22406b5b1..d27847a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the user account deletion flow in the user settings of the user account page - Improved the date formatting of the first activity in the historical market data table of the admin control panel +- Set the change detection strategy to `OnPush` in the activities page - Set the change detection strategy to `OnPush` in the allocations page - Set the change detection strategy to `OnPush` in the analysis page - Set the change detection strategy to `OnPush` in the portfolio holdings page +- Set the change detection strategy to `OnPush` in the activities 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 - Renamed the `SymbolProfileOverrides` _Prisma_ data model to `AssetProfileOverrides` while keeping the database table name diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 41ff570c2..91f4b6210 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -16,6 +16,7 @@ import { GfFabComponent } from '@ghostfolio/ui/fab'; import { DataService } from '@ghostfolio/ui/services'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, @@ -38,6 +39,7 @@ import { GfImportActivitiesDialogComponent } from './import-activities-dialog/im import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfActivitiesTableComponent, GfFabComponent, @@ -112,6 +114,8 @@ export class GfActivitiesPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + + this.changeDetectorRef.markForCheck(); }); this.userService.stateChanged From cae1e10769faa4eacf1be5970c35b27b9219790a Mon Sep 17 00:00:00 2001 From: Shaik Aftab Date: Thu, 9 Jul 2026 01:00:40 +0530 Subject: [PATCH 23/71] Task/validate property key in admin settings endpoint (#7247) * Validate property key * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/admin/admin.controller.ts | 9 ++++-- apps/api/src/app/admin/admin.service.ts | 8 ++--- .../src/app/admin/pipes/property-key.pipe.ts | 29 +++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 apps/api/src/app/admin/pipes/property-key.pipe.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d27847a94..d547ab964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set the change detection strategy to `OnPush` in the activities 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 +- Hardened the endpoint to update a property of the admin control panel by validating the `key` path parameter - Renamed the `SymbolProfileOverrides` _Prisma_ data model to `AssetProfileOverrides` while keeping the database table name - Improved the language localization for Dutch (`nl`) - Improved the language localization for French (`fr`) diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 019468dfd..26e0de0ac 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -29,7 +29,11 @@ import { ScraperConfiguration } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; -import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; +import type { + DateRange, + PropertyKey, + RequestWithUser +} from '@ghostfolio/common/types'; import { Body, @@ -55,6 +59,7 @@ import { isDate, parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AdminService } from './admin.service'; +import { PropertyKeyPipe } from './pipes/property-key.pipe'; @Controller('admin') export class AdminController { @@ -310,7 +315,7 @@ export class AdminController { @Put('settings/:key') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async updateProperty( - @Param('key') key: string, + @Param('key', PropertyKeyPipe) key: PropertyKey, @Body() data: UpdatePropertyDto ) { return this.adminService.putSetting(key, data.value); diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 81a48aff2..8325fa90e 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -344,17 +344,17 @@ export class AdminService { } } - public async putSetting(key: string, value: string) { + public async putSetting(key: PropertyKey, value: string) { let response: Property; if (value) { response = await this.propertyService.put({ - value, - key: key as PropertyKey + key, + value }); } else { response = await this.propertyService.delete({ - key: key as PropertyKey + key }); } diff --git a/apps/api/src/app/admin/pipes/property-key.pipe.ts b/apps/api/src/app/admin/pipes/property-key.pipe.ts new file mode 100644 index 000000000..a980b552b --- /dev/null +++ b/apps/api/src/app/admin/pipes/property-key.pipe.ts @@ -0,0 +1,29 @@ +import * as config from '@ghostfolio/common/config'; +import type { PropertyKey } from '@ghostfolio/common/types'; + +import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; + +@Injectable() +export class PropertyKeyPipe implements PipeTransform { + private readonly allowedKeys: Set; + + public constructor() { + this.allowedKeys = new Set( + Object.entries(config) + .filter(([key]) => { + return key.startsWith('PROPERTY_'); + }) + .map(([, value]) => { + return value as string; + }) + ); + } + + public transform(value: string): PropertyKey { + if (!this.allowedKeys.has(value)) { + throw new BadRequestException(`Invalid property key: ${value}`); + } + + return value as PropertyKey; + } +} From a7e721dbee17914d7b742aef4ffd285df9b60f4e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:31:25 +0200 Subject: [PATCH 24/71] Task/improve copy-to-clipboard in alert dialog (#7281) Improve copy-to-clipboard --- .../user-account-membership.component.ts | 2 +- libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 d086867aa..bdf2c3eb6 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 @@ -147,7 +147,7 @@ export class GfUserAccountMembershipComponent { .subscribe(({ apiKey }) => { this.notificationService.alert({ copyValue: apiKey, - discardLabel: $localize`Okay`, + discardLabel: $localize`Close`, message: $localize`Set this API key in your self-hosted environment:`, title: $localize`Ghostfolio Premium Data Provider API Key` }); diff --git a/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html index f690a8e5d..f09389680 100644 --- a/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html +++ b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html @@ -8,7 +8,7 @@
} @if (copyValue) { -
{{ copyValue }}
+
{{ copyValue }}
} } From e822e991f3529cb8eb7f6dc637fa7f704dd10bd4 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:34:34 +0200 Subject: [PATCH 25/71] Release 3.22.0 (#7282) --- 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 d547ab964..4fedaa3a3 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.22.0 - 2026-07-08 ### Added diff --git a/package-lock.json b/package-lock.json index aec72acd4..f7d55a2fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.21.0", + "version": "3.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.21.0", + "version": "3.22.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index a082e7c96..9bc8658a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.21.0", + "version": "3.22.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 7229d4759703447a1264b537fd240d081c6f6c61 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:54:45 +0700 Subject: [PATCH 26/71] Task/migrate deprecated @nx/webpack:webpack executor (#7198) * Migrate deprecated @nx/webpack:webpack executor * Update changelog --- CHANGELOG.md | 6 ++ apps/api/project.json | 26 +-------- apps/api/webpack.config.js | 51 +++++++++++++++-- nx.json | 10 ++++ package-lock.json | 111 ++++++++++++++++++++++++++++++++++++- package.json | 3 +- 6 files changed, 177 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fedaa3a3..ce8860e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Changed + +- Migrated the deprecated `@nx/webpack:webpack` executor to `@nx/webpack/plugin` + ## 3.22.0 - 2026-07-08 ### Added diff --git a/apps/api/project.json b/apps/api/project.json index 4e1affb13..c4c1be7ac 100644 --- a/apps/api/project.json +++ b/apps/api/project.json @@ -7,32 +7,10 @@ "generators": {}, "targets": { "build": { - "executor": "@nx/webpack:webpack", - "options": { - "compiler": "tsc", - "deleteOutputPath": false, - "main": "apps/api/src/main.ts", - "outputPath": "dist/apps/api", - "sourceMap": true, - "target": "node", - "tsConfig": "apps/api/tsconfig.app.json", - "webpackConfig": "apps/api/webpack.config.js" - }, "configurations": { - "production": { - "generatePackageJson": true, - "optimization": true, - "extractLicenses": true, - "inspect": false, - "fileReplacements": [ - { - "replace": "apps/api/src/environments/environment.ts", - "with": "apps/api/src/environments/environment.prod.ts" - } - ] - } + "production": {} }, - "outputs": ["{options.outputPath}"] + "outputs": ["{workspaceRoot}/dist/apps/api"] }, "copy-assets": { "executor": "nx:run-commands", diff --git a/apps/api/webpack.config.js b/apps/api/webpack.config.js index 2cc38b985..e59eee0bf 100644 --- a/apps/api/webpack.config.js +++ b/apps/api/webpack.config.js @@ -1,6 +1,49 @@ -const { composePlugins, withNx } = require('@nx/webpack'); +const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); +const path = require('path'); -module.exports = composePlugins(withNx(), (config, { options, context }) => { - // Customize webpack config here - return config; +// These options were migrated by @nx/webpack:convert-to-inferred from +// the project.json file and merged with the options in this file +const configValues = { + build: { + default: { + compiler: 'tsc', + deleteOutputPath: false, + main: './src/main.ts', + outputPath: 'dist/apps/api', + outputHashing: 'none', + sourceMap: true, + target: 'node', + tsConfig: './tsconfig.app.json' + }, + production: { + extractLicenses: true, + fileReplacements: [ + { + replace: path.resolve(__dirname, './src/environments/environment.ts'), + with: path.resolve( + __dirname, + './src/environments/environment.prod.ts' + ) + } + ], + generatePackageJson: true, + inspect: false, + optimization: true + } + } +}; + +// Determine the correct configValue to use based on the configuration +const configuration = process.env.NX_TASK_TARGET_CONFIGURATION || 'default'; + +const buildOptions = { + ...configValues.build.default, + ...configValues.build[configuration] +}; + +/** + * @type {import('webpack').WebpackOptionsNormalized} + */ +module.exports = async () => ({ + plugins: [new NxAppWebpackPlugin(buildOptions)] }); diff --git a/nx.json b/nx.json index 6b90f437c..1deb022df 100644 --- a/nx.json +++ b/nx.json @@ -67,6 +67,16 @@ } }, "$schema": "./node_modules/nx/schemas/nx-schema.json", + "plugins": [ + { + "plugin": "@nx/webpack/plugin", + "options": { + "buildTargetName": "build", + "serveTargetName": "serve", + "previewTargetName": "preview" + } + } + ], "targetDefaults": { "build": { "dependsOn": ["^build"], diff --git a/package-lock.json b/package-lock.json index f7d55a2fb..30467139c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -164,7 +164,8 @@ "ts-jest": "29.4.0", "ts-node": "10.9.2", "tslib": "2.8.1", - "typescript": "5.9.2" + "typescript": "5.9.2", + "webpack-cli": "7.1.0" }, "engines": { "node": ">=22.18.0" @@ -19378,6 +19379,19 @@ "node": ">=18" } }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -34613,6 +34627,101 @@ } } }, + "node_modules/webpack-cli": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.1.0.tgz", + "integrity": "sha512-pSJ5p5PkXRD88sfCq5Wo+coc42QykwRu5Md0DyESj0rT6PPPA2wTNabpHPKgqH8EMkfTDo3IWx3iiNXMu8XDBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^1.1.0", + "commander": "^14.0.3", + "cross-spawn": "^7.0.6", + "envinfo": "^7.21.0", + "import-local": "^3.2.0", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "js-yaml": "^4.0.0 || ^5.0.0", + "json5": "^2.2.3", + "toml": "^3.0.0 || ^4.0.0", + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0" + }, + "peerDependenciesMeta": { + "js-yaml": { + "optional": true + }, + "json5": { + "optional": true + }, + "toml": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz", + "integrity": "sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/webpack-cli/node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/webpack-dev-middleware": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", diff --git a/package.json b/package.json index 9bc8658a6..2de43ba7a 100644 --- a/package.json +++ b/package.json @@ -208,7 +208,8 @@ "ts-jest": "29.4.0", "ts-node": "10.9.2", "tslib": "2.8.1", - "typescript": "5.9.2" + "typescript": "5.9.2", + "webpack-cli": "7.1.0" }, "engines": { "node": ">=22.18.0" From 9a40720b620a58352fd68aa672b54df18193fd3b Mon Sep 17 00:00:00 2001 From: Arjun jaiswal <156437180+Arjun8242@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:50:02 +0530 Subject: [PATCH 27/71] Task/set change detection strategy to OnPush in home market component (#7277) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 1 + .../src/app/components/home-market/home-market.component.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce8860e2f..7a48a2787 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 - Migrated the deprecated `@nx/webpack:webpack` executor to `@nx/webpack/plugin` +- Set the change detection strategy to `OnPush` in the markets overview ## 3.22.0 - 2026-07-08 diff --git a/apps/client/src/app/components/home-market/home-market.component.ts b/apps/client/src/app/components/home-market/home-market.component.ts index 40b8f145b..6bf99b31d 100644 --- a/apps/client/src/app/components/home-market/home-market.component.ts +++ b/apps/client/src/app/components/home-market/home-market.component.ts @@ -14,6 +14,7 @@ import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { DataService } from '@ghostfolio/ui/services'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -27,6 +28,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DeviceDetectorService } from 'ngx-device-detector'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfBenchmarkComponent, GfFearAndGreedIndexComponent, From 2821658e29becbf75602fbe1c211bd3f6d278c01 Mon Sep 17 00:00:00 2001 From: qiukui666 <169086124+qiukui666@users.noreply.github.com> Date: Thu, 9 Jul 2026 23:31:44 +0800 Subject: [PATCH 28/71] Task/improve language localization for ZH (20260503) (#6835) * Update translations * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.zh.xlf | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a48a2787..d8c54fac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migrated the deprecated `@nx/webpack:webpack` executor to `@nx/webpack/plugin` - Set the change detection strategy to `OnPush` in the markets overview +- Improved the language localization for Chinese (`zh`) ## 3.22.0 - 2026-07-08 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 2230a6f86..0cc9a3202 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -1341,7 +1341,7 @@
Explore - Explore + 探索 apps/client/src/app/pages/resources/overview/resources-overview.component.html 11 @@ -1789,7 +1789,7 @@ Code - Code + 代码 apps/client/src/app/components/admin-overview/admin-overview.html 159 @@ -3129,7 +3129,7 @@ Duration - Duration + 持续时间 apps/client/src/app/components/admin-overview/admin-overview.html 172 @@ -5394,7 +5394,7 @@ Loan - Loan + 贷款 libs/ui/src/lib/i18n.ts 64 @@ -5714,7 +5714,7 @@ No Activities - No Activities + 暂无活动 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts 150 @@ -7167,7 +7167,7 @@ has been copied to the clipboard - has been copied to the clipboard + 已复制到剪贴板 apps/client/src/app/components/admin-overview/admin-overview.component.ts 388 From 7ca88702a9f6eb22847b16662f7a56449780d9ed Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:52:23 +0700 Subject: [PATCH 29/71] Task/improve type safety in user account membership component (#7286) * fix(client): resolve type errors * feat(client): enforce encapsulation * fix(client): remove unused variable * feat(client): enforce immutability * feat(client): replace constructor based DI with inject functions --- .../user-account-membership.component.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) 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 bdf2c3eb6..74ee2f64a 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 @@ -3,7 +3,6 @@ import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfMembershipCardComponent } from '@ghostfolio/ui/membership-card'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; @@ -14,7 +13,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - DestroyRef + DestroyRef, + inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; @@ -40,29 +40,29 @@ import { catchError } from 'rxjs/operators'; templateUrl: './user-account-membership.html' }) export class GfUserAccountMembershipComponent { - public baseCurrency: string; - public coupon: number; - public couponId: string; - public defaultDateFormat: string; - public durationExtension: StringValue; - public hasPermissionForSubscription: boolean; - public hasPermissionToCreateApiKey: boolean; - public hasPermissionToUpdateUserSettings: boolean; - public price: number; - public priceId: string; - public routerLinkPricing = publicRoutes.pricing.routerLink; - public trySubscriptionMail = + protected readonly baseCurrency: string; + protected coupon: number | undefined; + protected defaultDateFormat: string; + protected durationExtension: StringValue | undefined; + protected readonly hasPermissionForSubscription: boolean; + protected hasPermissionToCreateApiKey: boolean; + protected hasPermissionToUpdateUserSettings: boolean; + protected price: number; + protected readonly 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'; - public user: User; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private notificationService: NotificationService, - private snackBar: MatSnackBar, - private userService: UserService - ) { + protected user: User; + + private couponId: string | undefined; + private priceId: string; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly notificationService = inject(NotificationService); + private readonly snackBar = inject(MatSnackBar); + private readonly userService = inject(UserService); + + public constructor() { const { baseCurrency, globalPermissions } = this.dataService.fetchInfo(); this.baseCurrency = baseCurrency; @@ -104,7 +104,7 @@ export class GfUserAccountMembershipComponent { }); } - public onCheckout() { + protected onCheckout() { this.dataService .createStripeCheckoutSession({ couponId: this.couponId, @@ -125,7 +125,7 @@ export class GfUserAccountMembershipComponent { }); } - public onGenerateApiKey() { + protected onGenerateApiKey() { this.notificationService.confirm({ confirmFn: () => { this.dataService @@ -158,7 +158,7 @@ export class GfUserAccountMembershipComponent { }); } - public onRedeemCoupon() { + protected onRedeemCoupon() { this.notificationService.prompt({ confirmFn: (value) => { const couponCode = value?.trim(); From bc2cc8b6fdbeb3c9bc7b8207f3f7dfb41220d568 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:53:12 +0200 Subject: [PATCH 30/71] Task/set change detection strategy to OnPush in blog page components (#7288) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 1 + .../07/hallo-ghostfolio/hallo-ghostfolio-page.component.ts | 3 ++- .../07/hello-ghostfolio/hello-ghostfolio-page.component.ts | 3 ++- .../first-months-in-open-source-page.component.ts | 3 ++- .../ghostfolio-meets-internet-identity-page.component.ts | 3 ++- .../how-do-i-get-my-finances-in-order-page.component.ts | 3 ++- .../500-stars-on-github-page.component.ts | 3 ++- .../hacktoberfest-2022-page.component.ts | 3 ++- .../black-friday-2022/black-friday-2022-page.component.ts | 3 ++- ...ce-of-tracking-your-personal-finances-page.component.ts | 3 ++- .../ghostfolio-auf-sackgeld-vorgestellt-page.component.ts | 3 ++- .../ghostfolio-meets-umbrel-page.component.ts | 3 ++- .../1000-stars-on-github-page.component.ts | 3 ++- ...r-financial-potential-with-ghostfolio-page.component.ts | 3 ++- .../exploring-the-path-to-fire-page.component.ts | 3 ++- .../ghostfolio-joins-oss-friends-page.component.ts | 3 ++- .../2023/09/ghostfolio-2/ghostfolio-2-page.component.ts | 3 ++- .../hacktoberfest-2023-page.component.ts | 3 ++- .../11/black-week-2023/black-week-2023-page.component.ts | 3 ++- .../hacktoberfest-2023-debriefing-page.component.ts | 3 ++- .../hacktoberfest-2024-page.component.ts | 3 ++- .../11/black-weeks-2024/black-weeks-2024-page.component.ts | 3 ++- .../hacktoberfest-2025-page.component.ts | 3 ++- .../11/black-weeks-2025/black-weeks-2025-page.component.ts | 3 ++- .../2026/04/ghostfolio-3/ghostfolio-3-page.component.ts | 3 ++- apps/client/src/app/pages/blog/blog-page.component.ts | 7 ++++++- 26 files changed, 55 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c54fac3..2f0d44848 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 - Migrated the deprecated `@nx/webpack:webpack` executor to `@nx/webpack/plugin` +- Set the change detection strategy to `OnPush` in the blog page components - Set the change detection strategy to `OnPush` in the markets overview - Improved the language localization for Chinese (`zh`) diff --git a/apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.component.ts b/apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.component.ts index 597c8d998..ac6ec5c9e 100644 --- a/apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.component.ts +++ b/apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hallo-ghostfolio-page', diff --git a/apps/client/src/app/pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.component.ts b/apps/client/src/app/pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.component.ts index dd29cfd80..22fe85853 100644 --- a/apps/client/src/app/pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.component.ts +++ b/apps/client/src/app/pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hello-ghostfolio-page', diff --git a/apps/client/src/app/pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.component.ts b/apps/client/src/app/pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.component.ts index 91b05896d..2ab59f5ce 100644 --- a/apps/client/src/app/pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-first-months-in-open-source-page', diff --git a/apps/client/src/app/pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.component.ts b/apps/client/src/app/pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.component.ts index c410dd2b2..2f2409131 100644 --- a/apps/client/src/app/pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-ghostfolio-meets-internet-identity-page', diff --git a/apps/client/src/app/pages/blog/2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.component.ts b/apps/client/src/app/pages/blog/2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.component.ts index 812151600..f40170ba6 100644 --- a/apps/client/src/app/pages/blog/2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-how-do-i-get-my-finances-in-order-page', diff --git a/apps/client/src/app/pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.component.ts b/apps/client/src/app/pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.component.ts index 9004ac0e2..2627f4134 100644 --- a/apps/client/src/app/pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-500-stars-on-github-page', diff --git a/apps/client/src/app/pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.component.ts b/apps/client/src/app/pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.component.ts index fd34a6470..9e9fc2f8d 100644 --- a/apps/client/src/app/pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hacktoberfest-2022-page', diff --git a/apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.component.ts b/apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.component.ts index dd13d5b4d..2b57cb09e 100644 --- a/apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.component.ts @@ -1,11 +1,12 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], selector: 'gf-black-friday-2022-page', diff --git a/apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.component.ts b/apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.component.ts index 6e5a19f3c..4df232ea3 100644 --- a/apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.component.ts +++ b/apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-the-importance-of-tracking-your-personal-finances-page', diff --git a/apps/client/src/app/pages/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.component.ts b/apps/client/src/app/pages/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.component.ts index 53d670184..fb3f226ca 100644 --- a/apps/client/src/app/pages/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-ghostfolio-auf-sackgeld-vorgestellt-page', diff --git a/apps/client/src/app/pages/blog/2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.component.ts b/apps/client/src/app/pages/blog/2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.component.ts index cb8dffcd3..3533af5c4 100644 --- a/apps/client/src/app/pages/blog/2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-ghostfolio-meets-umbrel-page', diff --git a/apps/client/src/app/pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.component.ts b/apps/client/src/app/pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.component.ts index 60cc4177d..7a7d0197c 100644 --- a/apps/client/src/app/pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-1000-stars-on-github-page', diff --git a/apps/client/src/app/pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.component.ts b/apps/client/src/app/pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.component.ts index 5781674a6..de4d9a3c2 100644 --- a/apps/client/src/app/pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-unlock-your-financial-potential-with-ghostfolio-page', diff --git a/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.component.ts b/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.component.ts index 6c7bb2ae2..79adc0e93 100644 --- a/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-exploring-the-path-to-fire-page-page', diff --git a/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts b/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts index c5a9cf178..937212d0c 100644 --- a/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-ghostfolio-joins-oss-friends-page', diff --git a/apps/client/src/app/pages/blog/2023/09/ghostfolio-2/ghostfolio-2-page.component.ts b/apps/client/src/app/pages/blog/2023/09/ghostfolio-2/ghostfolio-2-page.component.ts index 197bc3e6b..3971b725b 100644 --- a/apps/client/src/app/pages/blog/2023/09/ghostfolio-2/ghostfolio-2-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/09/ghostfolio-2/ghostfolio-2-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-ghostfolio-2-page', diff --git a/apps/client/src/app/pages/blog/2023/09/hacktoberfest-2023/hacktoberfest-2023-page.component.ts b/apps/client/src/app/pages/blog/2023/09/hacktoberfest-2023/hacktoberfest-2023-page.component.ts index 1cf3f20a5..77d8f7ee0 100644 --- a/apps/client/src/app/pages/blog/2023/09/hacktoberfest-2023/hacktoberfest-2023-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/09/hacktoberfest-2023/hacktoberfest-2023-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hacktoberfest-2023-page', diff --git a/apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts b/apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts index 09c13cfa2..a4503c99b 100644 --- a/apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts @@ -1,11 +1,12 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], selector: 'gf-black-week-2023-page', diff --git a/apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts b/apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts index 7c8b37931..14ff67a04 100644 --- a/apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hacktoberfest-2023-debriefing-page', diff --git a/apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.component.ts b/apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.component.ts index 47f61adad..abfa39ffc 100644 --- a/apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.component.ts +++ b/apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hacktoberfest-2024-page', diff --git a/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts b/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts index d15f081f8..38b7b80c4 100644 --- a/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts +++ b/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts @@ -1,11 +1,12 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], selector: 'gf-black-weeks-2024-page', diff --git a/apps/client/src/app/pages/blog/2025/09/hacktoberfest-2025/hacktoberfest-2025-page.component.ts b/apps/client/src/app/pages/blog/2025/09/hacktoberfest-2025/hacktoberfest-2025-page.component.ts index 72990ca47..88c70723d 100644 --- a/apps/client/src/app/pages/blog/2025/09/hacktoberfest-2025/hacktoberfest-2025-page.component.ts +++ b/apps/client/src/app/pages/blog/2025/09/hacktoberfest-2025/hacktoberfest-2025-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-hacktoberfest-2025-page', diff --git a/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts index c5947abf4..b98be9c76 100644 --- a/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts +++ b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts @@ -1,11 +1,12 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], selector: 'gf-black-weeks-2025-page', diff --git a/apps/client/src/app/pages/blog/2026/04/ghostfolio-3/ghostfolio-3-page.component.ts b/apps/client/src/app/pages/blog/2026/04/ghostfolio-3/ghostfolio-3-page.component.ts index 63cd09d9c..e8e0c6da9 100644 --- a/apps/client/src/app/pages/blog/2026/04/ghostfolio-3/ghostfolio-3-page.component.ts +++ b/apps/client/src/app/pages/blog/2026/04/ghostfolio-3/ghostfolio-3-page.component.ts @@ -1,10 +1,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, RouterModule], selector: 'gf-ghostfolio-3-page', diff --git a/apps/client/src/app/pages/blog/blog-page.component.ts b/apps/client/src/app/pages/blog/blog-page.component.ts index 7f2c56d2d..f3227e3b0 100644 --- a/apps/client/src/app/pages/blog/blog-page.component.ts +++ b/apps/client/src/app/pages/blog/blog-page.component.ts @@ -1,7 +1,11 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { DataService } from '@ghostfolio/ui/services'; -import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA +} from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -9,6 +13,7 @@ import { addIcons } from 'ionicons'; import { chevronForwardOutline } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [IonIcon, MatCardModule, RouterModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], From 517b9a90e5169d60faaf990294e35fa6fcffcd35 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:53:55 +0200 Subject: [PATCH 31/71] Task/change wording in admin settings component (#7287) Change wording --- .../app/components/admin-settings/admin-settings.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8ee6250e5..643737c4d 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 @@ -76,7 +76,7 @@ newpopular } From d083c80ece9d1cd363a8200c8503f9b7e20ec30b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:54:53 +0200 Subject: [PATCH 32/71] Task/extend personal finance tools (20260709) (#7284) Extend personal finance tools --- libs/common/src/lib/personal-finance-tools.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 28b3f8495..b3aae6a02 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -14,6 +14,13 @@ export const personalFinanceTools: Product[] = [ slogan: 'Lifetime Personal Finance Control, One Single Payment.', url: 'https://www.mechcad.net' }, + { + categories: ['WEALTH_MANAGEMENT'], + key: 'akkuro', + name: 'Akkuro', + slogan: 'Composable Banking', + url: 'https://akkuro.com' + }, { categories: ['ETF_TRACKING', 'STOCK_TRACKING'], founded: 2023, @@ -215,7 +222,7 @@ export const personalFinanceTools: Product[] = [ name: 'Buxfer', origin: 'US', platforms: ['WEB'], - pricingPerYear: '$48', + pricingPerYear: '$96', regions: ['Global'], slogan: 'Take control of your financial future', url: 'https://www.buxfer.com' From 114597f820735832d3f5600702deb5ac0e25efbe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:57:09 +0200 Subject: [PATCH 33/71] Task/update locales (#7280) Co-authored-by: github-actions[bot] --- apps/client/src/locales/messages.ca.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.de.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.es.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.fr.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.it.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.ja.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.ko.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.nl.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.pl.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.pt.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.tr.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.uk.xlf | 76 ++++++++++++------------- apps/client/src/locales/messages.xlf | 74 ++++++++++++------------ apps/client/src/locales/messages.zh.xlf | 76 ++++++++++++------------- 14 files changed, 531 insertions(+), 531 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 3ef04ff96..27f3729fb 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -599,7 +599,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -623,7 +623,7 @@ Suprimir apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -907,7 +907,7 @@ Punts de referència apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -915,7 +915,7 @@ Divises apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -935,7 +935,7 @@ ETFs sense País apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -943,7 +943,7 @@ ETFs sense Sector apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -951,7 +951,7 @@ Filtra per... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -987,7 +987,7 @@ Nombre d’Activitats apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -995,7 +995,7 @@ Dades Històriques apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -1007,7 +1007,7 @@ Nombre de Sectors apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -1031,7 +1031,7 @@ Nombre de Països apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -1039,7 +1039,7 @@ Recopilar Dades del Perfil apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1919,7 +1919,7 @@ Por apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1935,7 +1935,7 @@ Cobdícia apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2969,10 +2969,6 @@ Okay D’acord - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -3454,6 +3450,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Duration @@ -4784,7 +4788,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -4804,11 +4808,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -4828,7 +4832,7 @@ Mensualment apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -4836,7 +4840,7 @@ Anualment apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -5933,7 +5937,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -6285,7 +6289,7 @@ No Activities apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -7043,6 +7047,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7273,7 +7281,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7695,7 +7703,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Gather Recent Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Gather All Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - new - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investment diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 313a22ce3..1ae8a60b8 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -238,7 +238,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -262,7 +262,7 @@ Löschen apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -526,7 +526,7 @@ Historische Daten apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -570,7 +570,7 @@ Letzte historische Marktdaten synchronisieren apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -578,7 +578,7 @@ Alle historischen Marktdaten synchronisieren apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -586,7 +586,7 @@ Profildaten synchronisieren apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1188,10 +1188,6 @@ Okay Okay - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -1741,6 +1737,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Dauer @@ -2506,7 +2510,7 @@ {VAR_PLURAL, plural, =1 {Profil} other {Profile}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -2686,7 +2690,7 @@ Monatlich apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -2738,7 +2742,7 @@ Anzahl Länder apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -2746,7 +2750,7 @@ Anzahl Sektoren apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -2770,7 +2774,7 @@ Angst apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -2786,7 +2790,7 @@ Gier apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2802,7 +2806,7 @@ Filtern nach... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -3342,7 +3346,7 @@ Anzahl Aktivitäten apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -3402,7 +3406,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -3538,7 +3542,7 @@ Jährlich apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -3606,7 +3610,7 @@ Keine Aktivitäten apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -4822,7 +4826,7 @@ ETFs ohne Länder apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -4830,7 +4834,7 @@ ETFs ohne Sektoren apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -5066,7 +5070,7 @@ Währungen apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -6248,11 +6252,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6709,7 +6713,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7067,6 +7071,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7297,7 +7305,7 @@ löschen apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7719,7 +7727,7 @@ KI-Anweisung wurde in die Zwischenablage kopiert apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7799,7 +7807,7 @@ Öffne Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8358,14 +8366,6 @@ 224 - - new - neu - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investition diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index e99def710..d3439f964 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -239,7 +239,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -263,7 +263,7 @@ Eliminar apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -527,7 +527,7 @@ Datos históricos apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -571,7 +571,7 @@ Recopilar datos del perfil apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1173,10 +1173,6 @@ Okay De acuerdo - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -1726,6 +1722,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Duración @@ -2491,7 +2495,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -2715,7 +2719,7 @@ Mensual apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -2723,7 +2727,7 @@ Número de sectores apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -2747,7 +2751,7 @@ Número de países apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -2755,7 +2759,7 @@ Miedo apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -2771,7 +2775,7 @@ Codicia apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2787,7 +2791,7 @@ Filtrar por... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -3327,7 +3331,7 @@ Número de operaciones apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -3379,7 +3383,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -3523,7 +3527,7 @@ Anual apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -3591,7 +3595,7 @@ Sin operaciones apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -4799,7 +4803,7 @@ ETFs sin países apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -4807,7 +4811,7 @@ ETFs sin sectores apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -5043,7 +5047,7 @@ Divisas apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -6225,11 +6229,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6686,7 +6690,7 @@ Índices de referencia apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7044,6 +7048,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7274,7 +7282,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7696,7 +7704,7 @@ El prompt para la IA ha sido copiado al portapapeles apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7776,7 +7784,7 @@ Abrir Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8013,7 +8021,7 @@ Recopilar datos históricos recientes del mercado apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8021,7 +8029,7 @@ Recopilar todos los datos históricos del mercado apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8359,14 +8367,6 @@ 224 - - new - nuevo - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Inversión diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 119ba3d02..41f6371f2 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -302,7 +302,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -326,7 +326,7 @@ Supprimer apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -570,7 +570,7 @@ Filtrer par... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -606,7 +606,7 @@ Nombre d’Activités apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -614,7 +614,7 @@ Données Historiques apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -626,7 +626,7 @@ Nombre de Secteurs apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -650,7 +650,7 @@ Nombre de Pays apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -658,7 +658,7 @@ Obtenir les Données du Profil apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1078,7 +1078,7 @@ Peur apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1094,7 +1094,7 @@ Avidité apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -1484,10 +1484,6 @@ Okay D’accord - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -2061,6 +2057,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Durée @@ -2614,7 +2618,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -2642,7 +2646,7 @@ Mensuel apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -3046,7 +3050,7 @@ {VAR_PLURAL, plural, =1 {Profil} other {Profils}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -3522,7 +3526,7 @@ Annuel apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -3590,7 +3594,7 @@ Aucune Activité apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -4798,7 +4802,7 @@ ETF sans Pays apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -4806,7 +4810,7 @@ ETF sans Secteurs apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -5042,7 +5046,7 @@ Devises apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -6224,11 +6228,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6685,7 +6689,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7043,6 +7047,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7273,7 +7281,7 @@ Supprimer apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7695,7 +7703,7 @@ Le prompt IA a été copié dans le presse-papiers apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Ouvrir Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Collecter les données récentes du marché historique apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Collecter toutes les données du marché historique apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - nouveau - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investissement diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 66c141baa..d3eb68c71 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -239,7 +239,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -263,7 +263,7 @@ Elimina apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -527,7 +527,7 @@ Dati storici apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -571,7 +571,7 @@ Raccogli i dati del profilo apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1173,10 +1173,6 @@ Okay Bene - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -1726,6 +1722,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Duration @@ -2491,7 +2495,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -2715,7 +2719,7 @@ Mensile apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -2723,7 +2727,7 @@ Numero di settori apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -2747,7 +2751,7 @@ Numero di paesi apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -2755,7 +2759,7 @@ Paura apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -2771,7 +2775,7 @@ Avidità apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2787,7 +2791,7 @@ Filtra per... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -3327,7 +3331,7 @@ Conteggio attività apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -3379,7 +3383,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -3523,7 +3527,7 @@ Annuale apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -3591,7 +3595,7 @@ No Activities apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -4799,7 +4803,7 @@ ETF senza paesi apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -4807,7 +4811,7 @@ ETF senza settori apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -5043,7 +5047,7 @@ Valute apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -6225,11 +6229,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6686,7 +6690,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7044,6 +7048,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7274,7 +7282,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7696,7 +7704,7 @@ L’AI prompt è stato copiato negli appunti apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7776,7 +7784,7 @@ Apri Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8013,7 +8021,7 @@ Raccogli dati storici di mercato recenti apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8021,7 +8029,7 @@ Raccogli tutti i dati storici di mercato apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8359,14 +8367,6 @@ 224 - - new - nuovo - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investimento diff --git a/apps/client/src/locales/messages.ja.xlf b/apps/client/src/locales/messages.ja.xlf index 0e72d5649..2a4ef7b7b 100644 --- a/apps/client/src/locales/messages.ja.xlf +++ b/apps/client/src/locales/messages.ja.xlf @@ -532,7 +532,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -556,7 +556,7 @@ 削除 apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -796,7 +796,7 @@ 通貨 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -816,7 +816,7 @@ 国に縛られないETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -824,7 +824,7 @@ セクター別ではないETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -844,7 +844,7 @@ 絞り込み... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -880,7 +880,7 @@ アクティビティ数 apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -888,7 +888,7 @@ 過去データ apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -900,7 +900,7 @@ セクター数 apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -924,7 +924,7 @@ 国の数 apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -932,7 +932,7 @@ 直近の市場実績データを収集する apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -940,7 +940,7 @@ すべての過去の市場データを収集する apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -948,7 +948,7 @@ プロファイルデータを収集する apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1664,7 +1664,7 @@ 恐怖 apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1680,7 +1680,7 @@ Greed apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2694,10 +2694,6 @@ Okay オッケー - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -3151,6 +3147,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration 持続時間 @@ -4440,7 +4444,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -4468,7 +4472,7 @@ 毎月 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -4476,7 +4480,7 @@ 年次 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -5413,7 +5417,7 @@ {VAR_PLURAL, plural, =1 {プロフィール} other {プロフィール}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -5741,7 +5745,7 @@ アクティビティなし apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -6273,11 +6277,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6710,7 +6714,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7060,6 +7064,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7298,7 +7306,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7720,7 +7728,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7800,7 +7808,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8359,14 +8367,6 @@ 224 - - new - new - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investment diff --git a/apps/client/src/locales/messages.ko.xlf b/apps/client/src/locales/messages.ko.xlf index b7d7cad22..f21f8fa02 100644 --- a/apps/client/src/locales/messages.ko.xlf +++ b/apps/client/src/locales/messages.ko.xlf @@ -532,7 +532,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -556,7 +556,7 @@ 삭제 apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -796,7 +796,7 @@ 통화 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -816,7 +816,7 @@ 국가 정보 없는 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -824,7 +824,7 @@ 섹터 정보 없는 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -844,7 +844,7 @@ 다음 기준으로 필터... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -880,7 +880,7 @@ 거래 건수 apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -888,7 +888,7 @@ 과거 데이터 apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -900,7 +900,7 @@ 섹터 수 apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -924,7 +924,7 @@ 국가 수 apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -932,7 +932,7 @@ 최근 과거 시장 데이터 수집 apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -940,7 +940,7 @@ 전체 과거 시장 데이터 수집 apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -948,7 +948,7 @@ 프로필 데이터 수집 apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1664,7 +1664,7 @@ 두려움 apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1680,7 +1680,7 @@ 탐욕 apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2694,10 +2694,6 @@ Okay 좋아요 - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -3151,6 +3147,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration 기간 @@ -4432,7 +4436,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -4460,7 +4464,7 @@ 월간 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -4468,7 +4472,7 @@ 매년 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -5405,7 +5409,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -5733,7 +5737,7 @@ 거래 내역 없음 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -6273,11 +6277,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6710,7 +6714,7 @@ 벤치마크 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7060,6 +7064,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7298,7 +7306,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7720,7 +7728,7 @@ AI 프롬프트가 클립보드에 복사되었습니다. apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7800,7 +7808,7 @@ 오픈 Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8359,14 +8367,6 @@ 224 - - new - 새로운 - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment 투자 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 0f02c4747..537bb8fdf 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -238,7 +238,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -262,7 +262,7 @@ Verwijderen apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -526,7 +526,7 @@ Historische gegevens apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -570,7 +570,7 @@ Verzamel profielgegevens apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1172,10 +1172,6 @@ Okay Oké - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -1725,6 +1721,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Looptijd @@ -2490,7 +2494,7 @@ {VAR_PLURAL, plural, =1 {Profiel} other {Profielen}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -2714,7 +2718,7 @@ Maandelijks apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -2722,7 +2726,7 @@ Aantal sectoren apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -2746,7 +2750,7 @@ Aantal landen apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -2754,7 +2758,7 @@ Angst apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -2770,7 +2774,7 @@ Hebzucht apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2786,7 +2790,7 @@ Filter op... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -3326,7 +3330,7 @@ Aantal activiteiten apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -3378,7 +3382,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -3522,7 +3526,7 @@ Jaarlijks apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -3590,7 +3594,7 @@ Geen activiteiten apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -4798,7 +4802,7 @@ ETF’s zonder Landen apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -4806,7 +4810,7 @@ ETF’s zonder Sectoren apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -5042,7 +5046,7 @@ Valuta apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -6224,11 +6228,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6685,7 +6689,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7043,6 +7047,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7273,7 +7281,7 @@ Verwijder apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7695,7 +7703,7 @@ AI-prompt is naar het klembord gekopieerd apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Verzamel Recente Marktgegevens apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Verzamel Alle Marktgegevens apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - nieuw - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investering diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 2c993890c..60b2f4115 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -523,7 +523,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -547,7 +547,7 @@ Usuń apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -787,7 +787,7 @@ Waluty apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -807,7 +807,7 @@ ETF-y bez Krajów apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -815,7 +815,7 @@ ETF-y bez Sektorów apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -835,7 +835,7 @@ Filtruj według... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -871,7 +871,7 @@ Liczba Aktywności apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -879,7 +879,7 @@ Dane Historyczne apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -891,7 +891,7 @@ Liczba Sektorów apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -915,7 +915,7 @@ Liczba Krajów apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -923,7 +923,7 @@ Zbierz Dane Profilu apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1631,7 +1631,7 @@ Zagrożenie apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1647,7 +1647,7 @@ Zachłanność apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2661,10 +2661,6 @@ Okay Okej - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -3118,6 +3114,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Duration @@ -4399,7 +4403,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -4427,7 +4431,7 @@ Miesięcznie apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -4435,7 +4439,7 @@ Rocznie apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -5328,7 +5332,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -5656,7 +5660,7 @@ Brak transakcji apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -6224,11 +6228,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6685,7 +6689,7 @@ Punkty Odniesienia apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7043,6 +7047,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7273,7 +7281,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7695,7 +7703,7 @@ Prompt AI został skopiowany do schowka apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Otwórz Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Zbierz najnowsze historyczne dane rynkowe apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Zbierz wszystkie historyczne dane rynkowe apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - nowy - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Inwestycja diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index af9f30ed2..56f2d5ab4 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -302,7 +302,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -326,7 +326,7 @@ Eliminar apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -570,7 +570,7 @@ Filtrar por... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -598,7 +598,7 @@ Dados Históricos apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -610,7 +610,7 @@ Contagem de Países apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -618,7 +618,7 @@ Contagem de Setores apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -642,7 +642,7 @@ Recolher Dados de Perfíl apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -930,7 +930,7 @@ Medo apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -946,7 +946,7 @@ Ganância apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -1472,10 +1472,6 @@ Okay OK - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -2037,6 +2033,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Duration @@ -2550,7 +2554,7 @@ Mensalmente apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -2938,7 +2942,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -3338,7 +3342,7 @@ Nº de Atividades apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -3454,7 +3458,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -3522,7 +3526,7 @@ Anualmente apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -3590,7 +3594,7 @@ No Activities apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -4798,7 +4802,7 @@ ETFs sem países apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -4806,7 +4810,7 @@ ETFs sem setores apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -5042,7 +5046,7 @@ Moedas apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -6224,11 +6228,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6685,7 +6689,7 @@ Referências apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7043,6 +7047,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7273,7 +7281,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7695,7 +7703,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Gather Recent Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Gather All Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - new - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Investment diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 8514610e5..9e43076f7 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -483,7 +483,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -507,7 +507,7 @@ Sil apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -751,7 +751,7 @@ Para Birimleri apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -771,7 +771,7 @@ Ülkesi Olmayan ETF’ler apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -779,7 +779,7 @@ Sektörü Olmayan ETF’ler apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -787,7 +787,7 @@ Filtrele... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -823,7 +823,7 @@ İşlem Sayısı apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -831,7 +831,7 @@ Tarihsel Veri apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -843,7 +843,7 @@ Sektör Sayısı apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -867,7 +867,7 @@ Ülke Sayısı apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -875,7 +875,7 @@ Profil Verisini Getir apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1479,7 +1479,7 @@ Korku apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1495,7 +1495,7 @@ Açgözlülük apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2185,10 +2185,6 @@ Okay Tamam - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -2610,6 +2606,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Duration @@ -3803,7 +3807,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -3831,7 +3835,7 @@ Aylık apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -3839,7 +3843,7 @@ Yıllık apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -4996,7 +5000,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -5324,7 +5328,7 @@ No Activities apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -6224,11 +6228,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6685,7 +6689,7 @@ Kıyaslamalar apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7043,6 +7047,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7273,7 +7281,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7695,7 +7703,7 @@ Yapay zeka istemi panoya kopyalandı apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Duck.ai’yi aç apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Yakın Geçmiş Piyasa Verilerini Topla apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Tüm Geçmiş Piyasa Verilerini Topla apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - yeni - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Yatırım diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 8152c9936..1a8a72690 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -623,7 +623,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -647,7 +647,7 @@ Видалити apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -879,7 +879,7 @@ Порівняльні показники apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -887,7 +887,7 @@ Валюти apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -907,7 +907,7 @@ ETF без країн apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -915,7 +915,7 @@ ETF без секторів apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -923,7 +923,7 @@ Фільтрувати за... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -983,7 +983,7 @@ Кількість активностей apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -991,7 +991,7 @@ Історичні дані apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -1003,7 +1003,7 @@ Кількість секторів apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -1027,7 +1027,7 @@ Кількість країн apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -1035,7 +1035,7 @@ Зібрати дані профілю apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2063,7 +2063,7 @@ Страх apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -2079,7 +2079,7 @@ Жадібність apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2857,10 +2857,6 @@ Okay ОК - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -3750,6 +3746,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration Тривалість @@ -5124,7 +5128,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -5144,11 +5148,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5168,7 +5172,7 @@ Щомісячно apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -5176,7 +5180,7 @@ Щорічно apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -6219,7 +6223,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -6739,7 +6743,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -7085,6 +7089,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7227,7 +7235,7 @@ No Activities apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -7703,7 +7711,7 @@ Запит AI скопійовано в буфер обміну apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7775,7 +7783,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8012,7 +8020,7 @@ Gather Recent Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8020,7 +8028,7 @@ Gather All Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8358,14 +8366,6 @@ 224 - - new - новий - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment Інвестиція diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index bf7304782..67d52db96 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -501,7 +501,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -524,7 +524,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -744,7 +744,7 @@ Currencies apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -762,14 +762,14 @@ ETFs without Countries apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 ETFs without Sectors apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -787,7 +787,7 @@ Filter by... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -820,14 +820,14 @@ Activities Count apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 Historical Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -838,7 +838,7 @@ Sectors Count apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -859,28 +859,28 @@ Countries Count apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 Gather Recent Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 Gather All Historical Market Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 Gather Profile Data apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1527,7 +1527,7 @@ Fear apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1542,7 +1542,7 @@ Greed apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2460,10 +2460,6 @@ Okay - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -2891,6 +2887,13 @@ 32 + + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration @@ -4051,7 +4054,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -4076,14 +4079,14 @@ Monthly apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 Yearly apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -4926,7 +4929,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -5226,7 +5229,7 @@ No Activities apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -5704,11 +5707,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6093,7 +6096,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -6416,6 +6419,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -6628,7 +6635,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7009,7 +7016,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7079,7 +7086,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -7579,13 +7586,6 @@ 224 - - new - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 0cc9a3202..025e5bb0e 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -532,7 +532,7 @@ apps/client/src/app/components/admin-market-data/admin-market-data.html - 278 + 284 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -556,7 +556,7 @@ 删除 apps/client/src/app/components/admin-market-data/admin-market-data.html - 301 + 307 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -796,7 +796,7 @@ 货币 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 135 + 132 apps/client/src/app/pages/public/public-page.html @@ -816,7 +816,7 @@ 没有国家的 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 140 + 137 @@ -824,7 +824,7 @@ 无行业类别的 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 145 + 142 @@ -844,7 +844,7 @@ 过滤... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 374 + 366 @@ -880,7 +880,7 @@ 活动计数 apps/client/src/app/components/admin-market-data/admin-market-data.html - 156 + 162 @@ -888,7 +888,7 @@ 历史数据 apps/client/src/app/components/admin-market-data/admin-market-data.html - 165 + 171 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -900,7 +900,7 @@ 行业数 apps/client/src/app/components/admin-market-data/admin-market-data.html - 174 + 180 @@ -924,7 +924,7 @@ 国家数 apps/client/src/app/components/admin-market-data/admin-market-data.html - 183 + 189 @@ -932,7 +932,7 @@ 收集个人资料数据 apps/client/src/app/components/admin-market-data/admin-market-data.html - 234 + 240 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -1640,7 +1640,7 @@ 恐惧 apps/client/src/app/components/home-market/home-market.component.ts - 46 + 48 apps/client/src/app/components/markets/markets.component.ts @@ -1656,7 +1656,7 @@ 贪婪 apps/client/src/app/components/home-market/home-market.component.ts - 47 + 49 apps/client/src/app/components/markets/markets.component.ts @@ -2670,10 +2670,6 @@ Okay 好的 - - apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 150 - apps/client/src/app/core/http-response.interceptor.ts 86 @@ -3127,6 +3123,14 @@ 32 + + popular + popular + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 79 + + Duration 持续时间 @@ -4416,7 +4420,7 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 78 + 80 libs/ui/src/lib/i18n.ts @@ -4444,7 +4448,7 @@ 每月 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 94 @@ -4452,7 +4456,7 @@ 每年 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 93 + 95 @@ -5389,7 +5393,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} apps/client/src/app/components/admin-market-data/admin-market-data.html - 249 + 255 @@ -5717,7 +5721,7 @@ 暂无活动 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 150 + 147 @@ -6249,11 +6253,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 82 + 84 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 98 + 100 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6686,7 +6690,7 @@ 基准 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 130 + 127 @@ -7044,6 +7048,10 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 80 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 150 + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 239 @@ -7274,7 +7282,7 @@ Delete apps/client/src/app/components/admin-market-data/admin-market-data.html - 244 + 250 @@ -7696,7 +7704,7 @@ AI 提示已复制到剪贴板 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 211 + 217 @@ -7776,7 +7784,7 @@ 打开 Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 212 + 218 @@ -8013,7 +8021,7 @@ 收集近期历史市场数据 apps/client/src/app/components/admin-market-data/admin-market-data.html - 225 + 231 @@ -8021,7 +8029,7 @@ 收集所有历史市场数据 apps/client/src/app/components/admin-market-data/admin-market-data.html - 230 + 236 @@ -8359,14 +8367,6 @@ 224 - - new - 新增 - - apps/client/src/app/components/admin-settings/admin-settings.component.html - 79 - - Investment 投资 From 61e9a761a9f9194bcff896f67ce5eebe9977ca9c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 9 Jul 2026 19:21:26 +0200 Subject: [PATCH 34/71] Task/set change detection strategy to OnPush in various page components (#7289) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 7 +++++++ .../src/app/pages/about/about-page.component.ts | 12 +++++++++--- .../src/app/pages/admin/admin-page.component.ts | 11 ++++++++++- apps/client/src/app/pages/faq/faq-page.component.ts | 3 ++- .../client/src/app/pages/home/home-page.component.ts | 6 ++++-- .../app/pages/resources/resources-page.component.ts | 3 ++- .../user-account/user-account-page.component.ts | 12 +++++++++--- apps/client/src/app/pages/zen/zen-page.component.ts | 12 +++++++++--- 8 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0d44848..d96f7dccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Migrated the deprecated `@nx/webpack:webpack` executor to `@nx/webpack/plugin` +- Set the change detection strategy to `OnPush` in the about page +- Set the change detection strategy to `OnPush` in the admin control panel - Set the change detection strategy to `OnPush` in the blog page components +- Set the change detection strategy to `OnPush` in the Frequently Asked Questions (FAQ) page +- Set the change detection strategy to `OnPush` in the home page - Set the change detection strategy to `OnPush` in the markets overview +- Set the change detection strategy to `OnPush` in the resources page +- Set the change detection strategy to `OnPush` in the user account page +- Set the change detection strategy to `OnPush` in the _Zen Mode_ - Improved the language localization for Chinese (`zh`) ## 3.22.0 - 2026-07-08 diff --git a/apps/client/src/app/pages/about/about-page.component.ts b/apps/client/src/app/pages/about/about-page.component.ts index 616977d80..615c0897d 100644 --- a/apps/client/src/app/pages/about/about-page.component.ts +++ b/apps/client/src/app/pages/about/about-page.component.ts @@ -8,7 +8,12 @@ import { } from '@ghostfolio/ui/page-tabs'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { addIcons } from 'ionicons'; import { @@ -21,6 +26,7 @@ import { } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-about-page', @@ -83,8 +89,6 @@ export class AboutPageComponent { }); this.user = state.user; - - this.changeDetectorRef.markForCheck(); } this.tabs.push({ @@ -92,6 +96,8 @@ export class AboutPageComponent { label: publicRoutes.about.subRoutes.ossFriends.title, routerLink: publicRoutes.about.subRoutes.ossFriends.routerLink }); + + this.changeDetectorRef.markForCheck(); }); addIcons({ diff --git a/apps/client/src/app/pages/admin/admin-page.component.ts b/apps/client/src/app/pages/admin/admin-page.component.ts index eb4ddb95c..15957d346 100644 --- a/apps/client/src/app/pages/admin/admin-page.component.ts +++ b/apps/client/src/app/pages/admin/admin-page.component.ts @@ -12,7 +12,12 @@ import { TabConfiguration } from '@ghostfolio/ui/page-tabs'; -import { Component, inject } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { addIcons } from 'ionicons'; import { @@ -24,6 +29,7 @@ import { } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-admin-page', @@ -35,6 +41,7 @@ export class AdminPageComponent { private user: User; + private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly tokenStorageService = inject(TokenStorageService); private readonly userService = inject(UserService); @@ -45,6 +52,8 @@ export class AdminPageComponent { this.user = state?.user; this.initializeTabs(); + + this.changeDetectorRef.markForCheck(); }); addIcons({ diff --git a/apps/client/src/app/pages/faq/faq-page.component.ts b/apps/client/src/app/pages/faq/faq-page.component.ts index 60081687a..17f70d138 100644 --- a/apps/client/src/app/pages/faq/faq-page.component.ts +++ b/apps/client/src/app/pages/faq/faq-page.component.ts @@ -6,11 +6,12 @@ import { } from '@ghostfolio/ui/page-tabs'; import { DataService } from '@ghostfolio/ui/services'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { addIcons } from 'ionicons'; import { cloudyOutline, readerOutline, serverOutline } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-faq-page', diff --git a/apps/client/src/app/pages/home/home-page.component.ts b/apps/client/src/app/pages/home/home-page.component.ts index 8c5caab22..574286c82 100644 --- a/apps/client/src/app/pages/home/home-page.component.ts +++ b/apps/client/src/app/pages/home/home-page.component.ts @@ -9,6 +9,7 @@ import { } from '@ghostfolio/ui/page-tabs'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, @@ -25,6 +26,7 @@ import { } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-home-page', @@ -85,9 +87,9 @@ export class GfHomePageComponent implements OnInit { : internalRoutes.home.subRoutes.markets.routerLink } ]; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); addIcons({ diff --git a/apps/client/src/app/pages/resources/resources-page.component.ts b/apps/client/src/app/pages/resources/resources-page.component.ts index 52980be85..d88aed4ba 100644 --- a/apps/client/src/app/pages/resources/resources-page.component.ts +++ b/apps/client/src/app/pages/resources/resources-page.component.ts @@ -4,7 +4,7 @@ import { TabConfiguration } from '@ghostfolio/ui/page-tabs'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { addIcons } from 'ionicons'; import { bookOutline, @@ -14,6 +14,7 @@ import { } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-resources-page', diff --git a/apps/client/src/app/pages/user-account/user-account-page.component.ts b/apps/client/src/app/pages/user-account/user-account-page.component.ts index ec7547b3c..486506675 100644 --- a/apps/client/src/app/pages/user-account/user-account-page.component.ts +++ b/apps/client/src/app/pages/user-account/user-account-page.component.ts @@ -6,12 +6,18 @@ import { TabConfiguration } from '@ghostfolio/ui/page-tabs'; -import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { addIcons } from 'ionicons'; import { diamondOutline, keyOutline, settingsOutline } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-user-account-page', @@ -52,9 +58,9 @@ export class GfUserAccountPageComponent { routerLink: internalRoutes.account.subRoutes.access.routerLink } ]; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); addIcons({ diamondOutline, keyOutline, settingsOutline }); diff --git a/apps/client/src/app/pages/zen/zen-page.component.ts b/apps/client/src/app/pages/zen/zen-page.component.ts index 4a897093c..0eb55ae26 100644 --- a/apps/client/src/app/pages/zen/zen-page.component.ts +++ b/apps/client/src/app/pages/zen/zen-page.component.ts @@ -6,12 +6,18 @@ import { TabConfiguration } from '@ghostfolio/ui/page-tabs'; -import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { addIcons } from 'ionicons'; import { albumsOutline, analyticsOutline } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPageTabsComponent], selector: 'gf-zen-page', @@ -44,9 +50,9 @@ export class GfZenPageComponent { } ]; this.user = state.user; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); addIcons({ albumsOutline, analyticsOutline }); From d10e815a3dd5c68e11415f4972fc4bed8efd2658 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:52:16 +0700 Subject: [PATCH 35/71] Task/improve type safety in portfolio summary component (#7293) * fix(client): resolve type errors * feat(client): enforce encapsulation * feat(client): replace constructor based DI with inject functions * feat(client): implement output signal --- .../portfolio-summary.component.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts index 3d2760202..b1e32c6c3 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts @@ -8,10 +8,10 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, Component, - EventEmitter, + inject, Input, OnChanges, - Output + output } from '@angular/core'; import { MatTooltipModule } from '@angular/material/tooltip'; import { IonIcon } from '@ionic/angular/standalone'; @@ -40,44 +40,46 @@ export class GfPortfolioSummaryComponent implements OnChanges { @Input() summary: PortfolioSummary; @Input() user: User; - @Output() emergencyFundChanged = new EventEmitter(); + public emergencyFundChanged = output(); - public buyAndSellActivitiesTooltip = translate( + protected readonly buyAndSellActivitiesTooltip = translate( 'BUY_AND_SELL_ACTIVITIES_TOOLTIP' ); - public precision = 2; - public timeInMarket: string; + protected precision = 2; + protected timeInMarket: string | undefined; - public get buyingPowerPercentage() { + private readonly notificationService = inject(NotificationService); + + public constructor() { + addIcons({ ellipsisHorizontalCircleOutline, informationCircleOutline }); + } + + protected get buyingPowerPercentage() { return this.summary?.totalValueInBaseCurrency ? this.summary.cash / this.summary.totalValueInBaseCurrency : 0; } - public get emergencyFundPercentage() { + protected get emergencyFundPercentage() { return this.summary?.totalValueInBaseCurrency ? (this.summary.emergencyFund?.total || 0) / this.summary.totalValueInBaseCurrency : 0; } - public get excludedFromAnalysisPercentage() { + protected get excludedFromAnalysisPercentage() { return this.summary?.totalValueInBaseCurrency ? this.summary.excludedAccountsAndActivities / this.summary.totalValueInBaseCurrency : 0; } - public constructor(private notificationService: NotificationService) { - addIcons({ ellipsisHorizontalCircleOutline, informationCircleOutline }); - } - public ngOnChanges() { if (this.summary) { if ( this.deviceType === 'mobile' && - this.summary.totalValueInBaseCurrency >= + (this.summary.totalValueInBaseCurrency ?? 0) >= NUMERICAL_PRECISION_THRESHOLD_6_FIGURES ) { this.precision = 0; @@ -98,7 +100,7 @@ export class GfPortfolioSummaryComponent implements OnChanges { } } - public onEditEmergencyFund() { + protected onEditEmergencyFund() { this.notificationService.prompt({ confirmFn: (value) => { const emergencyFund = parseFloat(value.trim()) || 0; From 4e6a1ede7be2d5e37de987f6ed0652954fe56569 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 10 Jul 2026 09:52:43 +0200 Subject: [PATCH 36/71] Task/improve language localization for DE (20260709) (#7291) * Update translation * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.de.xlf | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d96f7dccb..b0eb7bd50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set the change detection strategy to `OnPush` in the user account page - Set the change detection strategy to `OnPush` in the _Zen Mode_ - Improved the language localization for Chinese (`zh`) +- Improved the language localization for German (`de`) ## 3.22.0 - 2026-07-08 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 1ae8a60b8..eab9ec2cb 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -1739,7 +1739,7 @@ popular - popular + beliebt apps/client/src/app/components/admin-settings/admin-settings.component.html 79 From 2087ade01338578be6c5cb68ae5819ee3aec9359 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:38:19 +0200 Subject: [PATCH 37/71] Release 3.23.0 (#7299) --- 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 b0eb7bd50..51d049e9a 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.23.0 - 2026-07-10 ### Changed diff --git a/package-lock.json b/package-lock.json index 30467139c..d8c4974d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.22.0", + "version": "3.23.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.22.0", + "version": "3.23.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 2de43ba7a..261d35b54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.22.0", + "version": "3.23.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From ac8616856a152003c8ba4e8fd952e82096a17b63 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:33:16 +0200 Subject: [PATCH 38/71] Task/expose data source for Fear & Greed Index (#7285) * Expose data source for Fear & Greed Index * Update changelog --- CHANGELOG.md | 6 ++++ .../market-data/market-data.controller.ts | 6 ++-- .../market-data/market-data.module.ts | 8 ++++- apps/api/src/app/info/info.service.ts | 13 +++++--- .../configuration/configuration.service.ts | 3 ++ .../data-provider/data-provider.service.ts | 6 ++++ .../rapid-api/rapid-api.service.ts | 32 +++++++++++-------- .../interfaces/environment.interface.ts | 1 + .../twitter-bot/twitter-bot.module.ts | 8 ++++- .../twitter-bot/twitter-bot.service.ts | 10 +++--- libs/common/src/lib/config.ts | 1 - 11 files changed, 65 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51d049e9a..c139319a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Added + +- Exposed the `DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS` environment variable to set the data source of the _Fear & Greed Index_ (market mood) + ## 3.23.0 - 2026-07-10 ### Changed diff --git a/apps/api/src/app/endpoints/market-data/market-data.controller.ts b/apps/api/src/app/endpoints/market-data/market-data.controller.ts index 5dad0511f..105bb0780 100644 --- a/apps/api/src/app/endpoints/market-data/market-data.controller.ts +++ b/apps/api/src/app/endpoints/market-data/market-data.controller.ts @@ -1,11 +1,11 @@ import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { ghostfolioFearAndGreedIndexDataSourceCryptocurrencies, - ghostfolioFearAndGreedIndexDataSourceStocks, ghostfolioFearAndGreedIndexSymbolCryptocurrencies, ghostfolioFearAndGreedIndexSymbolStocks } from '@ghostfolio/common/config'; @@ -36,6 +36,7 @@ import { getReasonPhrase, StatusCodes } from 'http-status-codes'; @Controller('market-data') export class MarketDataController { public constructor( + private readonly dataProviderService: DataProviderService, private readonly marketDataService: MarketDataService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly symbolProfileService: SymbolProfileService, @@ -64,7 +65,8 @@ export class MarketDataController { this.symbolService.get({ includeHistoricalData, dataGatheringItem: { - dataSource: ghostfolioFearAndGreedIndexDataSourceStocks, + dataSource: + this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(), symbol: ghostfolioFearAndGreedIndexSymbolStocks }, useIntradayData: true diff --git a/apps/api/src/app/endpoints/market-data/market-data.module.ts b/apps/api/src/app/endpoints/market-data/market-data.module.ts index 1de10907b..47ce11502 100644 --- a/apps/api/src/app/endpoints/market-data/market-data.module.ts +++ b/apps/api/src/app/endpoints/market-data/market-data.module.ts @@ -1,4 +1,5 @@ import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module'; +import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { MarketDataModule as MarketDataServiceModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; @@ -8,6 +9,11 @@ import { MarketDataController } from './market-data.controller'; @Module({ controllers: [MarketDataController], - imports: [MarketDataServiceModule, SymbolModule, SymbolProfileModule] + imports: [ + DataProviderModule, + MarketDataServiceModule, + SymbolModule, + SymbolProfileModule + ] }) export class MarketDataModule {} diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 4441036a2..10836f7b8 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -4,6 +4,7 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { encodeDataSource } from '@ghostfolio/api/helper/data-source.helper'; import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { @@ -15,8 +16,7 @@ import { PROPERTY_GITHUB_STARGAZERS, PROPERTY_IS_READ_ONLY_MODE, PROPERTY_SLACK_COMMUNITY_USERS, - PROPERTY_UPTIME, - ghostfolioFearAndGreedIndexDataSourceStocks + PROPERTY_UPTIME } from '@ghostfolio/common/config'; import { InfoItem, Statistics } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; @@ -33,6 +33,7 @@ export class InfoService { public constructor( private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, + private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly jwtService: JwtService, private readonly propertyService: PropertyService, @@ -60,13 +61,15 @@ export class InfoService { } if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { + const fearAndGreedIndexDataSource = + this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(); + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { info.fearAndGreedDataSource = encodeDataSource( - ghostfolioFearAndGreedIndexDataSourceStocks + fearAndGreedIndexDataSource ); } else { - info.fearAndGreedDataSource = - ghostfolioFearAndGreedIndexDataSourceStocks; + info.fearAndGreedDataSource = fearAndGreedIndexDataSource; } globalPermissions.push(permissions.enableFearAndGreedIndex); diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index c96ccd946..b097627df 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -34,6 +34,9 @@ export class ConfigurationService { CACHE_QUOTES_TTL: num({ default: ms('1 minute') }), 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 + }), DATA_SOURCE_IMPORT: str({ default: DataSource.YAHOO }), DATA_SOURCES: json({ default: [DataSource.COINGECKO, DataSource.MANUAL, DataSource.YAHOO] diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index c6e9c83c1..8723466b0 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -177,6 +177,12 @@ export class DataProviderService implements OnModuleInit { ]; } + public getDataSourceForFearAndGreedIndexStocks(): DataSource { + return DataSource[ + this.configurationService.get('DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS') + ]; + } + public getDataSourceForImport(): DataSource { return DataSource[this.configurationService.get('DATA_SOURCE_IMPORT')]; } diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts index d8ba1ec67..e704f2861 100644 --- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts @@ -64,13 +64,15 @@ export class RapidApiService implements DataProviderInterface { if (symbol === ghostfolioFearAndGreedIndexSymbolStocks) { const fgi = await this.getFearAndGreedIndex(); - return { - [symbol]: { - [format(getYesterday(), DATE_FORMAT)]: { - marketPrice: fgi.previousClose.value + if (fgi) { + return { + [symbol]: { + [format(getYesterday(), DATE_FORMAT)]: { + marketPrice: fgi.previousClose.value + } } - } - }; + }; + } } } catch (error) { throw new Error( @@ -101,14 +103,16 @@ export class RapidApiService implements DataProviderInterface { if (symbol === ghostfolioFearAndGreedIndexSymbolStocks) { const fgi = await this.getFearAndGreedIndex(); - return { - [symbol]: { - currency: undefined, - dataSource: this.getName(), - marketPrice: fgi.now.value, - marketState: 'open' - } - }; + if (fgi) { + return { + [symbol]: { + currency: undefined, + dataSource: this.getName(), + marketPrice: fgi.now.value, + marketState: 'open' + } + }; + } } } catch (error) { this.logger.error(error); diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 89463e201..6147d4e45 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -13,6 +13,7 @@ export interface Environment extends CleanedEnvAccessors { CACHE_QUOTES_TTL: number; CACHE_TTL: number; DATA_SOURCE_EXCHANGE_RATES: string; + DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS: string; DATA_SOURCE_IMPORT: string; DATA_SOURCES: string[]; DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: string[]; diff --git a/apps/api/src/services/twitter-bot/twitter-bot.module.ts b/apps/api/src/services/twitter-bot/twitter-bot.module.ts index 80d53169c..bdb1a7988 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.module.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.module.ts @@ -1,13 +1,19 @@ import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module'; import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; +import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { TwitterBotService } from '@ghostfolio/api/services/twitter-bot/twitter-bot.service'; import { Module } from '@nestjs/common'; @Module({ exports: [TwitterBotService], - imports: [BenchmarkModule, ConfigurationModule, SymbolModule], + imports: [ + BenchmarkModule, + ConfigurationModule, + DataProviderModule, + SymbolModule + ], providers: [TwitterBotService] }) export class TwitterBotModule {} diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts index 366b016b6..8dffddf7b 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts @@ -1,10 +1,8 @@ import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service'; import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { - ghostfolioFearAndGreedIndexDataSourceStocks, - ghostfolioFearAndGreedIndexSymbolStocks -} from '@ghostfolio/common/config'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { ghostfolioFearAndGreedIndexSymbolStocks } from '@ghostfolio/common/config'; import { resolveFearAndGreedIndex, resolveMarketCondition @@ -24,6 +22,7 @@ export class TwitterBotService implements OnModuleInit { public constructor( private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, + private readonly dataProviderService: DataProviderService, private readonly symbolService: SymbolService ) {} @@ -49,7 +48,8 @@ export class TwitterBotService implements OnModuleInit { try { const symbolItem = await this.symbolService.get({ dataGatheringItem: { - dataSource: ghostfolioFearAndGreedIndexDataSourceStocks, + dataSource: + this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(), symbol: ghostfolioFearAndGreedIndexSymbolStocks } }); diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index c60170bbb..d613fb3cf 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -11,7 +11,6 @@ export const ghostfolioScraperApiSymbolPrefix = `_${ghostfolioPrefix}_`; export const ghostfolioFearAndGreedIndexDataSourceCryptocurrencies = DataSource.MANUAL; -export const ghostfolioFearAndGreedIndexDataSourceStocks = DataSource.RAPID_API; export const ghostfolioFearAndGreedIndexSymbolCryptocurrencies = `${ghostfolioPrefix}_FEAR_AND_GREED_INDEX_CRYPTOCURRENCIES`; export const ghostfolioFearAndGreedIndexSymbolStocks = `${ghostfolioPrefix}_FEAR_AND_GREED_INDEX_STOCKS`; From cb692f8d06f710274a9bfcb57a856d6b3fbbc320 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:33:55 +0700 Subject: [PATCH 39/71] Task/improve type safety in user detail dialog component (#7292) Improve type safety --- .../user-detail-dialog.component.ts | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) 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 73e09f612..637fe8c31 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 @@ -9,7 +9,7 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, - Inject, + inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -49,28 +49,30 @@ import { templateUrl: './user-detail-dialog.html' }) export class GfUserDetailDialogComponent implements OnInit { - public baseCurrency: string; - public readonly getCountryName = getCountryName; - public subscriptionsDataSource = new MatTableDataSource(); - public subscriptionsDisplayedColumns = [ + protected baseCurrency: string; + protected readonly getCountryName = getCountryName; + protected readonly subscriptionsDataSource = + new MatTableDataSource(); + protected readonly subscriptionsDisplayedColumns = [ 'createdAt', 'type', 'price', 'expiresAt' ]; - public user: AdminUserResponse; - - public constructor( - private adminService: AdminService, - private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, - private dataService: DataService, - private destroyRef: DestroyRef, - public dialogRef: MatDialogRef< - GfUserDetailDialogComponent, - UserDetailDialogResult - > - ) { + protected user: AdminUserResponse; + + protected readonly data = inject(MAT_DIALOG_DATA); + + private readonly adminService = inject(AdminService); + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly dialogRef = + inject>( + MatDialogRef + ); + + public constructor() { this.baseCurrency = this.dataService.fetchInfo().baseCurrency; addIcons({ @@ -98,14 +100,14 @@ export class GfUserDetailDialogComponent implements OnInit { }); } - public deleteUser() { + protected deleteUser() { this.dialogRef.close({ action: 'delete', userId: this.data.userId }); } - public getSum() { + protected getSum() { return getSum( this.subscriptionsDataSource.data .filter(({ price }) => { @@ -117,7 +119,7 @@ export class GfUserDetailDialogComponent implements OnInit { ).toNumber(); } - public getType({ createdAt, expiresAt, price }: Subscription) { + protected getType({ createdAt, expiresAt, price }: Subscription) { if (price) { return $localize`Paid`; } @@ -127,7 +129,7 @@ export class GfUserDetailDialogComponent implements OnInit { : $localize`Coupon`; } - public onClose() { + protected onClose() { this.dialogRef.close(); } } From 9cea437b2c1dff27541ef3788a85abadf68333c3 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:31:55 +0700 Subject: [PATCH 40/71] Task/improve type safety in user account registration dialog component (#7301) * fix(client): resolve type errors * feat(client): enforce encapsulation * feat(client): replace constructor based DI with inject functions * feat(client): implement viewChild signal --- ...r-account-registration-dialog.component.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts index cbbe2d29c..0265357bf 100644 --- a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts +++ b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts @@ -9,8 +9,8 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, - Inject, - ViewChild + inject, + viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -53,26 +53,28 @@ import { UserAccountRegistrationDialogParams } from './interfaces/interfaces'; templateUrl: 'user-account-registration-dialog.html' }) export class GfUserAccountRegistrationDialogComponent { - @ViewChild(MatStepper) stepper!: MatStepper; + protected readonly stepper = viewChild.required(MatStepper); - public accessToken: string; - public authToken: string; - public isCreateAccountButtonDisabled = true; - public isDisclaimerChecked = false; - public role: string; - public routerLinkAboutTermsOfService = + protected accessToken: string | undefined; + protected authToken: string; + protected isCreateAccountButtonDisabled = true; + protected isDisclaimerChecked = false; + protected role: string; + protected readonly routerLinkAboutTermsOfService = publicRoutes.about.subRoutes.termsOfService.routerLink; - public constructor( - private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) public data: UserAccountRegistrationDialogParams, - private dataService: DataService, - private destroyRef: DestroyRef - ) { + protected readonly data = + inject(MAT_DIALOG_DATA); + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + + public constructor() { addIcons({ arrowForwardOutline, checkmarkOutline, copyOutline }); } - public createAccount() { + protected createAccount() { this.dataService .postUser() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -81,17 +83,17 @@ export class GfUserAccountRegistrationDialogComponent { this.authToken = authToken; this.role = role; - this.stepper.next(); + this.stepper().next(); this.changeDetectorRef.markForCheck(); }); } - public enableCreateAccountButton() { + protected enableCreateAccountButton() { this.isCreateAccountButtonDisabled = false; } - public onChangeDislaimerChecked() { + protected onChangeDislaimerChecked() { this.isDisclaimerChecked = !this.isDisclaimerChecked; } } From d207d7a753c179aff3f1c2ad08875d5c679e4e04 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:33:32 +0200 Subject: [PATCH 41/71] Feature/add rate limiting to authentication and sign up endpoints (#7263) * Add rate limiting to authentication and sign up endpoints * Update changelog --- CHANGELOG.md | 2 + README.md | 43 +++++++-------- apps/api/src/app/app.module.ts | 53 ++++++++++++++++--- apps/api/src/app/auth/auth.controller.ts | 5 ++ .../src/app/redis-cache/redis-cache.module.ts | 11 +--- apps/api/src/app/user/user.controller.ts | 15 +++++- apps/api/src/guards/custom-throttler.guard.ts | 21 ++++++++ apps/api/src/helper/redis.helper.ts | 22 ++++++++ apps/api/src/main.ts | 20 +++++++ .../configuration/configuration.service.ts | 26 ++++++++- .../interfaces/environment.interface.ts | 2 + .../app/components/header/header.component.ts | 15 ++++-- .../src/app/core/http-response.interceptor.ts | 22 +++++--- libs/common/src/lib/config.ts | 5 ++ package-lock.json | 46 ++++++++++++++++ package.json | 2 + 16 files changed, 259 insertions(+), 51 deletions(-) create mode 100644 apps/api/src/guards/custom-throttler.guard.ts create mode 100644 apps/api/src/helper/redis.helper.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c139319a0..842a41aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Exposed the `DATA_SOURCE_FEAR_AND_GREED_INDEX_STOCKS` environment variable to set the data source of the _Fear & Greed Index_ (market mood) +- Exposed the `ENABLE_FEATURE_RATE_LIMITING` environment variable to control rate limiting for authentication and sign-up endpoints +- Exposed the `TRUST_PROXY` environment variable to determine the client IP address when running behind a reverse proxy ## 3.23.0 - 2026-07-10 diff --git a/README.md b/README.md index a9b3a3055..ce89412a3 100644 --- a/README.md +++ b/README.md @@ -85,27 +85,28 @@ We provide official container images hosted on [Docker Hub](https://hub.docker.c ### Supported Environment Variables -| Name | Type | Default Value | Description | -| --------------------------- | --------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ACCESS_TOKEN_SALT` | `string` | | A random string used as salt for access tokens | -| `API_KEY_COINGECKO_DEMO` | `string` (optional) |   | The _CoinGecko_ Demo API key | -| `API_KEY_COINGECKO_PRO` | `string` (optional) | | The _CoinGecko_ Pro API key | -| `DATABASE_URL` | `string` | | The database connection URL. If using a connection pooler, use the pooled connection URL here. e.g. `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}` | -| `DIRECT_URL` | `string` (optional) | | The direct database connection URL used by the _Prisma CLI_ (e.g. for schema migrations) and seeding, bypassing any connection poolers (falls back to `DATABASE_URL`) | -| `ENABLE_FEATURE_AUTH_TOKEN` | `boolean` (optional) | `true` | Enables authentication via security token | -| `HOST` | `string` (optional) | `0.0.0.0` | The host where the Ghostfolio application will run on | -| `JWT_SECRET_KEY` | `string` | | A random string used for _JSON Web Tokens_ (JWT) | -| `LOG_LEVELS` | `string[]` (optional) | | The logging levels for the Ghostfolio application, e.g. `["debug","error","log","warn"]` | -| `PORT` | `number` (optional) | `3333` | The port where the Ghostfolio application will run on | -| `POSTGRES_DB` | `string` | | The name of the _PostgreSQL_ database | -| `POSTGRES_PASSWORD` | `string` | | The password of the _PostgreSQL_ database | -| `POSTGRES_USER` | `string` | | The user of the _PostgreSQL_ database | -| `REDIS_DB` | `number` (optional) | `0` | The database index of _Redis_ | -| `REDIS_HOST` | `string` | | The host where _Redis_ is running | -| `REDIS_PASSWORD` | `string` | | The password of _Redis_ | -| `REDIS_PORT` | `number` | | The port where _Redis_ is running | -| `REQUEST_TIMEOUT` | `number` (optional) | `2000` | The timeout of network requests to data providers in milliseconds | -| `ROOT_URL` | `string` (optional) | `http://0.0.0.0:3333` | The root URL of the Ghostfolio application, used for generating callback URLs and external links. | +| Name | Type | Default Value | Description | +| --------------------------- | --------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ACCESS_TOKEN_SALT` | `string` | | A random string used as salt for access tokens | +| `API_KEY_COINGECKO_DEMO` | `string` (optional) |   | The _CoinGecko_ Demo API key | +| `API_KEY_COINGECKO_PRO` | `string` (optional) | | The _CoinGecko_ Pro API key | +| `DATABASE_URL` | `string` | | The database connection URL. If using a connection pooler, use the pooled connection URL here. e.g. `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}` | +| `DIRECT_URL` | `string` (optional) | | The direct database connection URL used by the _Prisma CLI_ (e.g. for schema migrations) and seeding, bypassing any connection poolers (falls back to `DATABASE_URL`) | +| `ENABLE_FEATURE_AUTH_TOKEN` | `boolean` (optional) | `true` | Enables authentication via security token | +| `HOST` | `string` (optional) | `0.0.0.0` | The host where the Ghostfolio application will run on | +| `JWT_SECRET_KEY` | `string` | | A random string used for _JSON Web Tokens_ (JWT) | +| `LOG_LEVELS` | `string[]` (optional) | | The logging levels for the Ghostfolio application, e.g. `["debug","error","log","warn"]` | +| `PORT` | `number` (optional) | `3333` | The port where the Ghostfolio application will run on | +| `POSTGRES_DB` | `string` | | The name of the _PostgreSQL_ database | +| `POSTGRES_PASSWORD` | `string` | | The password of the _PostgreSQL_ database | +| `POSTGRES_USER` | `string` | | The user of the _PostgreSQL_ database | +| `REDIS_DB` | `number` (optional) | `0` | The database index of _Redis_ | +| `REDIS_HOST` | `string` | | The host where _Redis_ is running | +| `REDIS_PASSWORD` | `string` | | The password of _Redis_ | +| `REDIS_PORT` | `number` | | The port where _Redis_ is running | +| `REQUEST_TIMEOUT` | `number` (optional) | `2000` | The timeout of network requests to data providers in milliseconds | +| `ROOT_URL` | `string` (optional) | `http://0.0.0.0:3333` | The root URL of the Ghostfolio application, used for generating callback URLs and external links. | +| `TRUST_PROXY` | `string` (optional) | | The [trust proxy](https://expressjs.com/en/guide/behind-proxies.html) setting of _Express.js_ to determine the client IP address for rate limiting, e.g. `1` if the Ghostfolio application runs behind a single reverse proxy | #### OpenID Connect OIDC (experimental) diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 04b1f3bf2..4bdd50c9e 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -1,7 +1,9 @@ import { EventsModule } from '@ghostfolio/api/events/events.module'; +import { getRedisConnectionOptions } from '@ghostfolio/api/helper/redis.helper'; import { BullBoardAuthMiddleware } from '@ghostfolio/api/middlewares/bull-board-auth.middleware'; import { HtmlTemplateMiddleware } from '@ghostfolio/api/middlewares/html-template.middleware'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { CronModule } from '@ghostfolio/api/services/cron/cron.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; @@ -13,18 +15,22 @@ import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/po import { BULL_BOARD_ROUTE, DEFAULT_LANGUAGE_CODE, - SUPPORTED_LANGUAGE_CODES + SUPPORTED_LANGUAGE_CODES, + THROTTLE_DEFAULT_LIMIT, + THROTTLE_DEFAULT_TTL } from '@ghostfolio/common/config'; import { ExpressAdapter } from '@bull-board/express'; import { BullBoardModule } from '@bull-board/nestjs'; +import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis'; import { BullModule } from '@nestjs/bull'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule } from '@nestjs/schedule'; import { ServeStaticModule } from '@nestjs/serve-static'; -import { StatusCodes } from 'http-status-codes'; +import { ThrottlerModule } from '@nestjs/throttler'; +import { getReasonPhrase, StatusCodes } from 'http-status-codes'; import { join } from 'node:path'; import { AccessModule } from './access/access.module'; @@ -95,12 +101,13 @@ import { UserModule } from './user/user.module'; middleware: BullBoardAuthMiddleware, route: BULL_BOARD_ROUTE }), - BullModule.forRoot({ - redis: { - db: parseInt(process.env.REDIS_DB ?? '0', 10), - host: process.env.REDIS_HOST, - password: process.env.REDIS_PASSWORD, - port: parseInt(process.env.REDIS_PORT ?? '6379', 10) + BullModule.forRootAsync({ + imports: [ConfigurationModule], + inject: [ConfigurationService], + useFactory: (configurationService: ConfigurationService) => { + return { + redis: getRedisConnectionOptions(configurationService) + }; } }), CacheModule, @@ -168,6 +175,36 @@ import { UserModule } from './user/user.module'; SubscriptionModule, SymbolModule, TagsModule, + ThrottlerModule.forRootAsync({ + imports: [ConfigurationModule], + inject: [ConfigurationService], + useFactory: (configurationService: ConfigurationService) => { + const isRateLimitingEnabled = configurationService.get( + 'ENABLE_FEATURE_RATE_LIMITING' + ); + + return { + errorMessage: getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS), + skipIf: () => { + return !isRateLimitingEnabled; + }, + storage: isRateLimitingEnabled + ? new ThrottlerStorageRedisService({ + ...getRedisConnectionOptions(configurationService), + // Reject commands immediately while Redis is unavailable + enableOfflineQueue: false, + maxRetriesPerRequest: 1 + }) + : undefined, + throttlers: [ + { + limit: THROTTLE_DEFAULT_LIMIT, + ttl: THROTTLE_DEFAULT_TTL + } + ] + }; + } + }), UserModule, WatchlistModule ], diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index 388f1dbd3..ac50f4b8a 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -1,4 +1,5 @@ import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service'; +import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; @@ -39,6 +40,7 @@ export class AuthController { * @deprecated */ @Get('anonymous/:accessToken') + @UseGuards(CustomThrottlerGuard) public async accessTokenLoginGet( @Param('accessToken') accessToken: string ): Promise { @@ -55,6 +57,7 @@ export class AuthController { } @Post('anonymous') + @UseGuards(CustomThrottlerGuard) public async accessTokenLogin( @Body() body: { accessToken: string } ): Promise { @@ -135,6 +138,7 @@ export class AuthController { } @Post('webauthn/generate-authentication-options') + @UseGuards(CustomThrottlerGuard) public async generateAuthenticationOptions( @Body() body: { deviceId: string } ) { @@ -156,6 +160,7 @@ export class AuthController { } @Post('webauthn/verify-authentication') + @UseGuards(CustomThrottlerGuard) public async verifyAuthentication( @Body() body: { deviceId: string; credential: AssertionCredentialJSON } ) { diff --git a/apps/api/src/app/redis-cache/redis-cache.module.ts b/apps/api/src/app/redis-cache/redis-cache.module.ts index d0e3228b7..8d56c7c51 100644 --- a/apps/api/src/app/redis-cache/redis-cache.module.ts +++ b/apps/api/src/app/redis-cache/redis-cache.module.ts @@ -1,3 +1,4 @@ +import { getRedisConnectionUrl } from '@ghostfolio/api/helper/redis.helper'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; @@ -14,16 +15,8 @@ import { RedisCacheService } from './redis-cache.service'; imports: [ConfigurationModule], inject: [ConfigurationService], useFactory: async (configurationService: ConfigurationService) => { - const redisPassword = encodeURIComponent( - configurationService.get('REDIS_PASSWORD') - ); - return { - stores: [ - createKeyv( - `redis://${redisPassword ? `:${redisPassword}` : ''}@${configurationService.get('REDIS_HOST')}:${configurationService.get('REDIS_PORT')}/${configurationService.get('REDIS_DB')}` - ) - ], + stores: [createKeyv(getRedisConnectionUrl(configurationService))], ttl: configurationService.get('CACHE_TTL') }; } diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 6346ce43a..7a1fc71dc 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -1,11 +1,16 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; +import { CustomThrottlerGuard } from '@ghostfolio/api/guards/custom-throttler.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; -import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; +import { + HEADER_KEY_IMPERSONATION, + THROTTLE_SIGNUP_LIMIT, + THROTTLE_SIGNUP_TTL +} from '@ghostfolio/common/config'; import { DeleteOwnUserDto, UpdateOwnAccessTokenDto, @@ -37,6 +42,7 @@ import { import { REQUEST } from '@nestjs/core'; import { JwtService } from '@nestjs/jwt'; import { AuthGuard } from '@nestjs/passport'; +import { Throttle } from '@nestjs/throttler'; import { User as UserModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { merge, size } from 'lodash'; @@ -128,6 +134,13 @@ export class UserController { } @Post() + @Throttle({ + default: { + limit: THROTTLE_SIGNUP_LIMIT, + ttl: THROTTLE_SIGNUP_TTL + } + }) + @UseGuards(CustomThrottlerGuard) public async signupUser(): Promise { const isUserSignupEnabled = await this.propertyService.isUserSignupEnabled(); diff --git a/apps/api/src/guards/custom-throttler.guard.ts b/apps/api/src/guards/custom-throttler.guard.ts new file mode 100644 index 000000000..c4f0e806d --- /dev/null +++ b/apps/api/src/guards/custom-throttler.guard.ts @@ -0,0 +1,21 @@ +import { ExecutionContext, Injectable, Logger } from '@nestjs/common'; +import { ThrottlerException, ThrottlerGuard } from '@nestjs/throttler'; + +@Injectable() +export class CustomThrottlerGuard extends ThrottlerGuard { + private readonly logger = new Logger(CustomThrottlerGuard.name); + + public async canActivate(context: ExecutionContext): Promise { + try { + return await super.canActivate(context); + } catch (error) { + if (error instanceof ThrottlerException) { + throw error; + } + + this.logger.error(error); + + return true; + } + } +} diff --git a/apps/api/src/helper/redis.helper.ts b/apps/api/src/helper/redis.helper.ts new file mode 100644 index 000000000..81fe905eb --- /dev/null +++ b/apps/api/src/helper/redis.helper.ts @@ -0,0 +1,22 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; + +export function getRedisConnectionOptions( + configurationService: ConfigurationService +) { + return { + db: configurationService.get('REDIS_DB'), + host: configurationService.get('REDIS_HOST'), + password: configurationService.get('REDIS_PASSWORD'), + port: configurationService.get('REDIS_PORT') + }; +} + +export function getRedisConnectionUrl( + configurationService: ConfigurationService +): string { + const { db, host, password, port } = + getRedisConnectionOptions(configurationService); + const encodedPassword = encodeURIComponent(password); + + return `redis://${encodedPassword ? `:${encodedPassword}` : ''}@${host}:${port}/${db}`; +} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 63185a48b..33ad032e9 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,3 +1,4 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { BULL_BOARD_ROUTE, DEFAULT_HOST, @@ -39,6 +40,8 @@ async function bootstrap() { ) as LogLevel[]; } catch {} + await configApp.close(); + const app = await NestFactory.create(AppModule, { logger: customLogLevels ?? @@ -97,6 +100,23 @@ async function bootstrap() { }); } + const configurationService = app.get(ConfigurationService); + + const trustProxy = configurationService.get('TRUST_PROXY'); + + if (trustProxy) { + app.set('trust proxy', trustProxy); + } + + if ( + configurationService.get('ENABLE_FEATURE_RATE_LIMITING') && + trustProxy === '' + ) { + logger.warn( + 'Rate limiting is enabled, but TRUST_PROXY is not set. If the Ghostfolio application runs behind a reverse proxy, the rate limits are shared across all clients.' + ); + } + const HOST = configService.get('HOST') || DEFAULT_HOST; const PORT = configService.get('PORT') || DEFAULT_PORT; diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index b097627df..497dc6040 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -13,9 +13,31 @@ import { import { Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; -import { bool, cleanEnv, host, json, num, port, str, url } from 'envalid'; +import { + bool, + cleanEnv, + host, + json, + makeValidator, + num, + port, + str, + url +} from 'envalid'; import ms from 'ms'; +const trustProxy = makeValidator((input) => { + if (/^\d+$/.test(input)) { + return Number(input); + } else if (input === 'false') { + return false; + } else if (input === 'true') { + return true; + } + + return input; +}); + @Injectable() export class ConfigurationService { private readonly environmentConfiguration: Environment; @@ -50,6 +72,7 @@ export class ConfigurationService { ENABLE_FEATURE_CRON: bool({ default: true }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: bool({ default: true }), + ENABLE_FEATURE_RATE_LIMITING: bool({ default: false }), ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }), ENABLE_FEATURE_STATISTICS: bool({ default: false }), ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }), @@ -114,6 +137,7 @@ export class ConfigurationService { default: environment.rootUrl }), STRIPE_SECRET_KEY: str({ default: '' }), + TRUST_PROXY: trustProxy({ default: '' }), TWITTER_ACCESS_TOKEN: str({ default: 'dummyAccessToken' }), TWITTER_ACCESS_TOKEN_SECRET: str({ default: 'dummyAccessTokenSecret' }), TWITTER_API_KEY: str({ default: 'dummyApiKey' }), diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 6147d4e45..bb58b01ab 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -23,6 +23,7 @@ export interface Environment extends CleanedEnvAccessors { ENABLE_FEATURE_CRON: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean; + ENABLE_FEATURE_RATE_LIMITING: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean; ENABLE_FEATURE_STATISTICS: boolean; ENABLE_FEATURE_SUBSCRIPTION: boolean; @@ -57,6 +58,7 @@ export interface Environment extends CleanedEnvAccessors { REQUEST_TIMEOUT: number; ROOT_URL: string; STRIPE_SECRET_KEY: string; + TRUST_PROXY: boolean | number | string; TWITTER_ACCESS_TOKEN: string; TWITTER_ACCESS_TOKEN_SECRET: string; TWITTER_API_KEY: string; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index c12509d58..8e70c1a8c 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -22,6 +22,7 @@ import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; +import { HttpErrorResponse } from '@angular/common/http'; import { ChangeDetectionStrategy, Component, @@ -42,6 +43,7 @@ import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; import { MatToolbarModule } from '@angular/material/toolbar'; import { Router, RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; +import { StatusCodes } from 'http-status-codes'; import { addIcons } from 'ionicons'; import { closeOutline, @@ -315,10 +317,15 @@ export class GfHeaderComponent implements OnChanges { this.dataService .loginAnonymous(data?.accessToken) .pipe( - catchError(() => { - this.notificationService.alert({ - title: $localize`Oops! Incorrect Security Token.` - }); + catchError((error: HttpErrorResponse) => { + if (error.status !== StatusCodes.TOO_MANY_REQUESTS) { + // The notification for too many requests is handled in the + // HttpResponseInterceptor + + this.notificationService.alert({ + title: $localize`Oops! Incorrect Security Token.` + }); + } return EMPTY; }), diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts index 17927a924..7385e090c 100644 --- a/apps/client/src/app/core/http-response.interceptor.ts +++ b/apps/client/src/app/core/http-response.interceptor.ts @@ -98,15 +98,23 @@ export class HttpResponseInterceptor implements HttpInterceptor { }); } } else if (error.status === StatusCodes.TOO_MANY_REQUESTS) { - if (!this.snackBarRef) { - this.snackBarRef = this.snackBar.open( - $localize`Oops! It looks like you’re making too many requests. Please slow down a bit.` - ); + // Replace an already visible snack bar so that the rate limiting + // feedback is not swallowed + const snackBarRef = this.snackBar.open( + $localize`Oops! It looks like you’re making too many requests. Please slow down a bit.`, + undefined, + { + duration: ms('6 seconds') + } + ); - this.snackBarRef?.afterDismissed().subscribe(() => { + snackBarRef.afterDismissed().subscribe(() => { + if (this.snackBarRef === snackBarRef) { this.snackBarRef = undefined; - }); - } + } + }); + + this.snackBarRef = snackBarRef; } else if (error.status === StatusCodes.UNAUTHORIZED) { if (!error.url?.includes('/data-providers/ghostfolio/status')) { if (this.webAuthnService.isEnabled()) { diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index d613fb3cf..633e13f24 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -329,4 +329,9 @@ export const TAG_ID_EXCLUDE_FROM_ANALYSIS = 'f2e868af-8333-459f-b161-cbc6544c24bd'; export const TAG_ID_DEMO = 'efa08cb3-9b9d-4974-ac68-db13a19c4874'; +export const THROTTLE_DEFAULT_LIMIT = 10; +export const THROTTLE_DEFAULT_TTL = ms('1 minute'); +export const THROTTLE_SIGNUP_LIMIT = 5; +export const THROTTLE_SIGNUP_TTL = ms('1 hour'); + export const UNKNOWN_KEY = 'UNKNOWN'; diff --git a/package-lock.json b/package-lock.json index d8c4974d7..41c915bd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@internationalized/number": "3.6.7", "@ionic/angular": "8.8.12", "@keyv/redis": "5.1.6", + "@nest-lab/throttler-storage-redis": "1.2.0", "@nestjs/bull": "11.0.4", "@nestjs/cache-manager": "3.1.3", "@nestjs/common": "11.1.27", @@ -40,6 +41,7 @@ "@nestjs/platform-express": "11.1.27", "@nestjs/schedule": "6.1.3", "@nestjs/serve-static": "5.0.5", + "@nestjs/throttler": "6.5.0", "@openrouter/ai-sdk-provider": "2.9.1", "@prisma/adapter-pg": "7.8.0", "@prisma/client": "7.8.0", @@ -6956,6 +6958,39 @@ "tslib": "^2.4.0" } }, + "node_modules/@nest-lab/throttler-storage-redis": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@nest-lab/throttler-storage-redis/-/throttler-storage-redis-1.2.0.tgz", + "integrity": "sha512-tMkUyo68NCKTR+zILk+EC35SMYBtDPZY2mCj7ZaCietWGVTnuP4zwq9ERYfvU6kJv6h8teNZrC6MJCmY6/dljw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/throttler": ">=6.0.0", + "ioredis": ">=5.0.0", + "reflect-metadata": "^0.2.1" + }, + "peerDependenciesMeta": { + "@nestjs/common": { + "optional": false + }, + "@nestjs/core": { + "optional": false + }, + "@nestjs/throttler": { + "optional": false + }, + "ioredis": { + "optional": false + }, + "reflect-metadata": { + "optional": false + } + } + }, "node_modules/@nestjs/bull": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@nestjs/bull/-/bull-11.0.4.tgz", @@ -7441,6 +7476,17 @@ } } }, + "node_modules/@nestjs/throttler": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@nestjs/throttler/-/throttler-6.5.0.tgz", + "integrity": "sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0" + } + }, "node_modules/@ngtools/webpack": { "version": "21.2.6", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.2.6.tgz", diff --git a/package.json b/package.json index 261d35b54..ae7d29e6e 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@internationalized/number": "3.6.7", "@ionic/angular": "8.8.12", "@keyv/redis": "5.1.6", + "@nest-lab/throttler-storage-redis": "1.2.0", "@nestjs/bull": "11.0.4", "@nestjs/cache-manager": "3.1.3", "@nestjs/common": "11.1.27", @@ -84,6 +85,7 @@ "@nestjs/platform-express": "11.1.27", "@nestjs/schedule": "6.1.3", "@nestjs/serve-static": "5.0.5", + "@nestjs/throttler": "6.5.0", "@openrouter/ai-sdk-provider": "2.9.1", "@prisma/adapter-pg": "7.8.0", "@prisma/client": "7.8.0", From 0afd17773bdc048ebcd22b18263425da264a945d Mon Sep 17 00:00:00 2001 From: moduvoice Date: Sat, 11 Jul 2026 15:21:23 +0700 Subject: [PATCH 42/71] Task/improve language localization for KO (#7279) * Update translations * Update changelog --- CHANGELOG.md | 4 ++ apps/client/src/locales/messages.ko.xlf | 94 ++++++++++++------------- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 842a41aeb..34573918f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Exposed the `ENABLE_FEATURE_RATE_LIMITING` environment variable to control rate limiting for authentication and sign-up endpoints - Exposed the `TRUST_PROXY` environment variable to determine the client IP address when running behind a reverse proxy +### Changed + +- Improved the language localization for Korean (`ko`) + ## 3.23.0 - 2026-07-10 ### Changed diff --git a/apps/client/src/locales/messages.ko.xlf b/apps/client/src/locales/messages.ko.xlf index f21f8fa02..ea2df8596 100644 --- a/apps/client/src/locales/messages.ko.xlf +++ b/apps/client/src/locales/messages.ko.xlf @@ -433,7 +433,7 @@ Copy - Copy + 복사 libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html 20 @@ -605,7 +605,7 @@ Paid - Paid + 유료 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 122 @@ -693,7 +693,7 @@ and is driven by the efforts of its contributors - 기여자들의 노력으로 발전하고 있습니다 + 기여자들의 노력으로 발전하고 있습니다 apps/client/src/app/pages/about/overview/about-overview-page.html 50 @@ -769,7 +769,7 @@ Watch the Ghostfol.io Trailer on YouTube - Watch the Ghostfol.io Trailer on YouTube + YouTube에서 Ghostfol.io 트레일러 보기 apps/client/src/app/pages/landing/landing-page.html 19 @@ -805,7 +805,7 @@ Category - Category + 카테고리 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 77 @@ -869,7 +869,7 @@ Data Gathering Frequency - Data Gathering Frequency + 데이터 수집 빈도 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 454 @@ -913,7 +913,7 @@ Subscription History - Subscription History + 구독 내역 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 136 @@ -1097,7 +1097,7 @@ Total - Total + 합계 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 155 @@ -1721,7 +1721,7 @@ The source code is fully available as open source software (OSS) under the AGPL-3.0 license - 소스 코드는 오픈 소스 소프트웨어로 완전히 공개되어 있으며, AGPL-3.0 라이선스 하에 제공됩니다 + 소스 코드는 오픈 소스 소프트웨어로 완전히 공개되어 있으며, AGPL-3.0 라이선스 하에 제공됩니다 apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -2293,7 +2293,7 @@ Ghostfolio in Numbers: Monthly Active Users (MAU) - Ghostfolio in Numbers: Monthly Active Users (MAU) + Ghostfolio 통계: 월간 활성 사용자 수(MAU) apps/client/src/app/pages/landing/landing-page.html 63 @@ -2333,7 +2333,7 @@ The value has been copied to the clipboard - The value has been copied to the clipboard + 값이 클립보드에 복사되었습니다. libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts 46 @@ -2493,7 +2493,7 @@ Coupon - Coupon + 쿠폰 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 127 @@ -2529,7 +2529,7 @@ Close Account - Close Account + 계정 폐쇄 apps/client/src/app/components/user-account-settings/user-account-settings.html 337 @@ -2549,7 +2549,7 @@ Contributors to Ghostfolio - Contributors to Ghostfolio + Ghostfolio의 기여자 apps/client/src/app/pages/about/overview/about-overview-page.html 54 @@ -2885,7 +2885,7 @@ Financial Planning - Financial Planning + 재무 설계 libs/ui/src/lib/i18n.ts 108 @@ -3221,7 +3221,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. + 보안을 위해 Ghostfolio 계정을 폐쇄하려면 먼저 모든 거래 내역과 계좌를 삭제해 주세요. apps/client/src/app/components/user-account-settings/user-account-settings.html 348 @@ -3349,7 +3349,7 @@ Creation - Creation + 생성 apps/client/src/app/components/admin-overview/admin-overview.html 185 @@ -3389,7 +3389,7 @@ just now - just now + 방금 전 apps/client/src/app/components/admin-users/admin-users.component.ts 217 @@ -3713,7 +3713,7 @@ Oops! Could not delete the asset profiles. - Oops! Could not delete the asset profiles. + 이런! 자산 프로필을 삭제할 수 없습니다. apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 52 @@ -3801,7 +3801,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. - Ghostfolio는 투명성을 핵심 가치로 삼습니다. 우리는 소스 코드를 오픈 소스 소프트웨어로 공개하며, AGPL-3.0 라이선스 하에 배포합니다. 또한 플랫폼 운영 현황에 대한 집계된 핵심 지표를 공개적으로 공유합니다. + Ghostfolio는 투명성을 핵심 가치로 삼습니다. 우리는 소스 코드를 오픈 소스 소프트웨어로 공개하며, AGPL-3.0 라이선스 하에 배포합니다. 또한 플랫폼 운영 현황에 대한 집계된 핵심 지표를 공개적으로 공유합니다. apps/client/src/app/pages/open/open-page.html 7 @@ -3897,7 +3897,7 @@ Available on - Available on + 이용 가능 플랫폼 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 130 @@ -4205,7 +4205,7 @@ Price - Price + 가격 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 171 @@ -4305,7 +4305,7 @@ Trial - Trial + 체험판 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts 126 @@ -4613,7 +4613,7 @@ Hourly - Hourly + 매시간 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 214 @@ -4725,7 +4725,7 @@ Expiration - Expiration + 만료 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 204 @@ -5034,7 +5034,7 @@ Upgrade to Ghostfolio Premium - Upgrade to Ghostfolio Premium + Ghostfolio 프리미엄으로 업그레이드 libs/ui/src/lib/premium-indicator/premium-indicator.component.html 4 @@ -5122,7 +5122,7 @@ Stock Tracking - Stock Tracking + 주식 추적 libs/ui/src/lib/i18n.ts 111 @@ -5146,7 +5146,7 @@ Web - Web + libs/ui/src/lib/i18n.ts 120 @@ -5342,7 +5342,7 @@ Portfolio Filters - Portfolio Filters + 포트폴리오 필터 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 63 @@ -5406,7 +5406,7 @@ {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} - {VAR_PLURAL, plural, =1 {Profile} other {Profiles}} + {VAR_PLURAL, plural, =1 {프로필} other {프로필}} apps/client/src/app/components/admin-market-data/admin-market-data.html 255 @@ -5718,7 +5718,7 @@ Ghostfolio in Numbers: Pulls on Docker Hub - Ghostfolio in Numbers: Pulls on Docker Hub + Ghostfolio 통계: Docker Hub 다운로드 수 apps/client/src/app/pages/landing/landing-page.html 101 @@ -6350,7 +6350,7 @@ Expires () - Expires () + 만료 () apps/client/src/app/components/admin-users/admin-users.html 34 @@ -6511,7 +6511,7 @@ Daily - Daily + 매일 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 210 @@ -6811,7 +6811,7 @@ Oops! Could not delete the asset profile. - Oops! Could not delete the asset profile. + 이런! 자산 프로필을 삭제할 수 없습니다. apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 51 @@ -6931,7 +6931,7 @@ Do you really want to delete these asset profiles? - Do you really want to delete these asset profiles? + 개의 자산 프로필을 정말 삭제하시겠습니까? apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 67 @@ -7159,7 +7159,7 @@ Performance with currency effect Performance - 환율 효과 반영 수익률 수익률 + 환율 효과 반영 수익률 수익률 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 83 @@ -7175,7 +7175,7 @@ Tax Reporting - Tax Reporting + 세금 보고 libs/ui/src/lib/i18n.ts 112 @@ -7183,7 +7183,7 @@ Change with currency effect Change - 환율 효과 반영 변동 변동 + 환율 효과 반영 변동 변동 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 63 @@ -7199,7 +7199,7 @@ Compare Ghostfolio to - - Compare Ghostfolio to - + Ghostfolio와 비교 - apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html 32 @@ -7239,7 +7239,7 @@ Dividend Tracking - Dividend Tracking + 배당 추적 libs/ui/src/lib/i18n.ts 105 @@ -7303,7 +7303,7 @@ Delete - Delete + 삭제 apps/client/src/app/components/admin-market-data/admin-market-data.html 250 @@ -7419,7 +7419,7 @@ Investment Research - Investment Research + 투자 리서치 libs/ui/src/lib/i18n.ts 109 @@ -7701,7 +7701,7 @@ Net Worth Tracking - Net Worth Tracking + 순자산 추적 libs/ui/src/lib/i18n.ts 110 @@ -7833,7 +7833,7 @@ Ghostfolio in Numbers: Stars on GitHub - Ghostfolio in Numbers: Stars on GitHub + Ghostfolio 통계: GitHub 스타 수 apps/client/src/app/pages/landing/landing-page.html 82 @@ -8139,7 +8139,7 @@ ETF Tracking - ETF Tracking + ETF 추적 libs/ui/src/lib/i18n.ts 106 @@ -8305,7 +8305,7 @@ Post to Ghostfolio on X (formerly Twitter) - Post to Ghostfolio on X (formerly Twitter) + X(이전의 Twitter)에서 Ghostfolio에 게시하세요. apps/client/src/app/pages/about/overview/about-overview-page.html 85 @@ -8526,7 +8526,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_ - 버그가 발생하거나 개선 사항이나 새로운 기능을 제안하고 싶다면 Ghostfolio 슬랙 커뮤니티에 가입하고 @ghostfolio_에 게시하세요. + 버그가 발생하거나 개선 사항이나 새로운 기능을 제안하고 싶다면 Ghostfolio 슬랙 커뮤니티에 가입하고 @ghostfolio_에 게시하세요. apps/client/src/app/pages/about/overview/about-overview-page.html 71 From 618e22e3fa8cbfcbb28961ae928364373d325a8b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:03:09 +0200 Subject: [PATCH 43/71] Task/round value in fear and greed index component (#7303) * Round value * Update changelog --- CHANGELOG.md | 1 + .../fear-and-greed-index/fear-and-greed-index.component.html | 2 +- .../fear-and-greed-index/fear-and-greed-index.component.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34573918f..b97416560 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 ### Changed +- Rounded the value of the _Fear & Greed Index_ (market mood) - Improved the language localization for Korean (`ko`) ## 3.23.0 - 2026-07-10 diff --git a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html b/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html index 67274ae38..dd2925f43 100644 --- a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html +++ b/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html @@ -5,7 +5,7 @@
{{ fearAndGreedIndexText }} {{ fearAndGreedIndex }}{{ fearAndGreedIndex | number: '1.0-0' }}/100
diff --git a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts b/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts index 32e2cc29a..b507f0008 100644 --- a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts +++ b/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts @@ -1,6 +1,7 @@ import { resolveFearAndGreedIndex } from '@ghostfolio/common/helper'; import { translate } from '@ghostfolio/ui/i18n'; +import { DecimalPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -11,7 +12,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - imports: [NgxSkeletonLoaderModule], + imports: [DecimalPipe, NgxSkeletonLoaderModule], selector: 'gf-fear-and-greed-index', styleUrls: ['./fear-and-greed-index.component.scss'], templateUrl: './fear-and-greed-index.component.html' From 4ff3e87a68d0c3b39869cfaae006d016d87450ae Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:05:07 +0200 Subject: [PATCH 44/71] Release 3.24.0 (#7304) --- 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 b97416560..4210b2ddd 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.24.0 - 2026-07-11 ### Added diff --git a/package-lock.json b/package-lock.json index 41c915bd1..051e79839 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.23.0", + "version": "3.24.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.23.0", + "version": "3.24.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index ae7d29e6e..4c400b8af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.23.0", + "version": "3.24.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From c679e791dca98c5a19d30c12aeaae5ec782fc31c Mon Sep 17 00:00:00 2001 From: Cole Munz <145523609+munzzyy@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:19:17 -0500 Subject: [PATCH 45/71] Bugfix/corrupted state attributes in CA and TR locales (#7297) Fix states --- apps/client/src/locales/messages.ca.xlf | 2 +- apps/client/src/locales/messages.tr.xlf | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 27f3729fb..c1d6f9d8b 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -4073,7 +4073,7 @@
How does Ghostfolio work? - Com ho fa Ghostfolio treballar? + Com ho fa Ghostfolio treballar? apps/client/src/app/pages/landing/landing-page.html 286 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 9e43076f7..1acc6ea9e 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -2096,7 +2096,7 @@ 1Y - 1Y + 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 232 @@ -4273,7 +4273,7 @@ This overview page features a curated collection of personal finance tools compared to the open source alternative Ghostfolio. If you value transparency, data privacy, and community collaboration, Ghostfolio provides an excellent opportunity to take control of your financial management. - Bu genel bakış sayfası, diğer kişisel finans araçlarının seçilmiş bir koleksiyonunun açık kaynak alternatifiGhostfolio ile karşılaştırmasını sunmaktadır.. Şeffaflığa, veri gizliliğine ve topluluk işbirliğine değer veriyorsanız Ghostfolio, finansal yönetiminizin kontrolünü ele almak için mükemmel Şeffaflığa, veri gizliliğine ve topluluk işbirliğine değer veriyorsanız Ghostfolio, finansal yönetiminizin kontrolünü ele almak için mükemmel bir fırsat sunuyor. + Bu genel bakış sayfası, diğer kişisel finans araçlarının seçilmiş bir koleksiyonunun açık kaynak alternatifiGhostfolio ile karşılaştırmasını sunmaktadır.. Şeffaflığa, veri gizliliğine ve topluluk işbirliğine değer veriyorsanız Ghostfolio, finansal yönetiminizin kontrolünü ele almak için mükemmel bir fırsat sunuyor. apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html 9 From c2706a6dba77d8f3e8d89d94c0713655d7052d73 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 12 Jul 2026 09:25:51 +0200 Subject: [PATCH 46/71] Bugfix/fix display of assets without currency in assistant and symbol autocomplete component (#7306) * Fix display of assets without currency * Update changelog --- CHANGELOG.md | 7 +++++++ .../assistant/assistant-list-item/assistant-list-item.html | 5 ++++- .../symbol-autocomplete/symbol-autocomplete.component.html | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4210b2ddd..d2a5d24e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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 + +### Fixed + +- 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 + ## 3.24.0 - 2026-07-11 ### Added diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html index 36179b719..fa30d0c03 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html @@ -8,7 +8,10 @@ @if (item && isAsset(item)) {
{{ item?.symbol ?? '' | gfSymbol }} · {{ item?.currency }} + >{{ item?.symbol ?? '' | gfSymbol }} + @if (item.currency) { + · {{ item.currency }} + } @if (item?.assetSubClassString) { · {{ item.assetSubClassString }} } diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html index 456cd9940..12867662b 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html @@ -25,7 +25,10 @@ } {{ lookupItem.symbol | gfSymbol }} · {{ lookupItem.currency }} + >{{ lookupItem.symbol | gfSymbol }} + @if (lookupItem.currency) { + · {{ lookupItem.currency }} + } @if (lookupItem.assetSubClass) { · {{ lookupItem.assetSubClassString }} } From d0d458347dcedc6f2f68b3074b2eeda7e3037264 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 12 Jul 2026 09:27:26 +0200 Subject: [PATCH 47/71] Task/simplify webauthn page (#7305) Simplify webauthn page --- apps/client/src/app/app.routes.ts | 3 +-- .../src/app/pages/webauthn/webauthn-page.component.ts | 3 +-- apps/client/src/app/pages/webauthn/webauthn-page.html | 6 ------ .../src/lib/routes/interfaces/internal-route.interface.ts | 2 +- libs/common/src/lib/routes/routes.ts | 3 +-- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/apps/client/src/app/app.routes.ts b/apps/client/src/app/app.routes.ts index 9588cee68..2c0591b2c 100644 --- a/apps/client/src/app/app.routes.ts +++ b/apps/client/src/app/app.routes.ts @@ -128,8 +128,7 @@ export const routes: Routes = [ import('./pages/webauthn/webauthn-page.component').then( (c) => c.GfWebauthnPageComponent ), - path: internalRoutes.webauthn.path, - title: internalRoutes.webauthn.title + path: internalRoutes.webauthn.path }, { path: internalRoutes.zen.path, diff --git a/apps/client/src/app/pages/webauthn/webauthn-page.component.ts b/apps/client/src/app/pages/webauthn/webauthn-page.component.ts index 8e7e58fd1..d988e1bb9 100644 --- a/apps/client/src/app/pages/webauthn/webauthn-page.component.ts +++ b/apps/client/src/app/pages/webauthn/webauthn-page.component.ts @@ -1,6 +1,5 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; -import { GfLogoComponent } from '@ghostfolio/ui/logo'; import { ChangeDetectorRef, @@ -15,7 +14,7 @@ import { Router } from '@angular/router'; @Component({ host: { class: 'page' }, - imports: [GfLogoComponent, MatButtonModule, MatProgressSpinnerModule], + imports: [MatButtonModule, MatProgressSpinnerModule], selector: 'gf-webauthn-page', styleUrls: ['./webauthn-page.scss'], templateUrl: './webauthn-page.html' diff --git a/apps/client/src/app/pages/webauthn/webauthn-page.html b/apps/client/src/app/pages/webauthn/webauthn-page.html index 308a7096b..a5cce9700 100644 --- a/apps/client/src/app/pages/webauthn/webauthn-page.html +++ b/apps/client/src/app/pages/webauthn/webauthn-page.html @@ -1,12 +1,6 @@
-
- -
- @if (!hasError) {
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 48/71] 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 49/71] 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 50/71] 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 51/71] 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 52/71] 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 53/71] 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 54/71] 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 55/71] 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", From fd61a1e62b28fa54ec2b55c9eb775f166b6229a3 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:19:21 +0700 Subject: [PATCH 56/71] Task/enable strict null checks in apps/client (#7309) * fix(client): resolve type errors * feat(ui): resolve type error in assistant component * feat(client): enable strict null checks * feat(client): enforce encapsulation * feat(client): enforce immutability * feat(client): replace constructor based DI with inject functions * fix(client): prevent race condition in nested subscription --- .../rule-settings-dialog.html | 4 +- .../activities/activities-page.component.ts | 203 +++++++++--------- ...ate-or-update-activity-dialog.component.ts | 8 +- .../interfaces/interfaces.ts | 4 +- .../activities/interfaces/interfaces.ts | 7 + apps/client/tsconfig.json | 1 + .../src/lib/assistant/assistant.component.ts | 2 +- 7 files changed, 124 insertions(+), 105 deletions(-) create mode 100644 apps/client/src/app/pages/portfolio/activities/interfaces/interfaces.ts diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html index d81a3c1f3..618626b63 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7,8 +7,8 @@ >
@if ( - data.rule.configuration.thresholdMin && - data.rule.configuration.thresholdMax + data.rule.configuration?.thresholdMin && + data.rule.configuration?.thresholdMax ) {
diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 91f4b6210..397fdce57 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -20,6 +20,7 @@ import { ChangeDetectorRef, Component, DestroyRef, + inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -31,12 +32,14 @@ import { MatTableDataSource } from '@angular/material/table'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { format, parseISO } from 'date-fns'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subscription } from 'rxjs'; +import { of } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; import { GfCreateOrUpdateActivityDialogComponent } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component'; import { CreateOrUpdateActivityDialogParams } from './create-or-update-activity-dialog/interfaces/interfaces'; import { GfImportActivitiesDialogComponent } from './import-activities-dialog/import-activities-dialog.component'; import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; +import { ActivitiesPageParams } from './interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -51,54 +54,53 @@ import { ImportActivitiesDialogParams } from './import-activities-dialog/interfa templateUrl: './activities-page.html' }) export class GfActivitiesPageComponent implements OnInit { - public activityTypesFilter: string[] = []; - public dataSource: MatTableDataSource; - public deviceType: string; - public hasImpersonationId: boolean; - public hasPermissionToCreateActivity: boolean; - public hasPermissionToDeleteActivity: boolean; - public pageIndex = 0; - public pageSize = DEFAULT_PAGE_SIZE; - public routeQueryParams: Subscription; - public sortColumn = 'date'; - public sortDirection: SortDirection = 'desc'; - public totalItems: number | undefined; - public user: User; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private dialog: MatDialog, - private icsService: IcsService, - private impersonationStorageService: ImpersonationStorageService, - private route: ActivatedRoute, - private router: Router, - private userService: UserService - ) { - this.routeQueryParams = route.queryParams - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((params) => { - if (params['createDialog']) { - if (params['activityId']) { - this.dataService - .fetchActivity(params['activityId']) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((activity) => { - this.openCreateActivityDialog(activity); - }); - } else { - this.openCreateActivityDialog(); + protected dataSource: MatTableDataSource | undefined; + protected deviceType: string; + protected hasImpersonationId: boolean; + protected hasPermissionToCreateActivity: boolean; + protected hasPermissionToDeleteActivity: boolean; + protected pageIndex = 0; + protected readonly pageSize = DEFAULT_PAGE_SIZE; + protected sortColumn = 'date'; + protected sortDirection: SortDirection = 'desc'; + protected totalItems: number | undefined; + protected user: User; + + private activityTypesFilter: string[] = []; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly dialog = inject(MatDialog); + private readonly icsService = inject(IcsService); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { + this.route.queryParams + .pipe( + takeUntilDestroyed(this.destroyRef), + switchMap((params: ActivitiesPageParams) => { + if (params.activityId && (params.createDialog || params.editDialog)) { + return this.dataService + .fetchActivity(params.activityId) + .pipe(map((activity) => ({ activity, params }))); } - } else if (params['editDialog']) { - if (params['activityId']) { - this.dataService - .fetchActivity(params['activityId']) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((activity) => { - this.openUpdateActivityDialog(activity); - }); + + return of({ params, activity: undefined }); + }) + ) + .subscribe(({ activity, params }) => { + if (params.createDialog) { + this.openCreateActivityDialog(activity); + } else if (params.editDialog) { + if (activity) { + this.openUpdateActivityDialog(activity); } else { this.router.navigate(['.'], { relativeTo: this.route }); } @@ -131,49 +133,13 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public fetchActivities() { - // Reset dataSource and totalItems to show loading state - this.dataSource = undefined; - this.totalItems = undefined; - - const dateRange = this.user?.settings?.dateRange; - const range = this.isCalendarYear(dateRange) ? dateRange : undefined; - - this.dataService - .fetchActivities({ - range, - activityTypes: this.activityTypesFilter.length - ? this.activityTypesFilter - : undefined, - filters: this.userService.getFilters(), - skip: this.pageIndex * this.pageSize, - sortColumn: this.sortColumn, - sortDirection: this.sortDirection, - take: this.pageSize - }) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ activities, count }) => { - this.dataSource = new MatTableDataSource(activities); - this.totalItems = count; - - if ( - this.hasPermissionToCreateActivity && - this.user?.activitiesCount === 0 - ) { - this.router.navigate([], { queryParams: { createDialog: true } }); - } - - this.changeDetectorRef.markForCheck(); - }); - } - - public onChangePage(page: PageEvent) { + protected onChangePage(page: PageEvent) { this.pageIndex = page.pageIndex; this.fetchActivities(); } - public onClickActivity({ dataSource, symbol }: AssetProfileIdentifier) { + protected onClickActivity({ dataSource, symbol }: AssetProfileIdentifier) { this.router.navigate([], { queryParams: { dataSource, @@ -183,11 +149,11 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onCloneActivity(aActivity: Activity) { + protected onCloneActivity(aActivity: Activity) { this.openCreateActivityDialog(aActivity); } - public onDeleteActivities() { + protected onDeleteActivities() { this.dataService .deleteActivities({ filters: this.userService.getFilters() @@ -205,7 +171,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onDeleteActivity(aId: string) { + protected onDeleteActivity(aId: string) { this.dataService .deleteActivity(aId) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -221,7 +187,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onExport(activityIds?: string[]) { + protected onExport(activityIds?: string[]) { let fetchExportParams: any = { activityIds }; if (!activityIds) { @@ -238,7 +204,7 @@ export class GfActivitiesPageComponent implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { for (const activity of data.activities) { - delete activity.id; + delete (activity as Omit & { id?: string }).id; } downloadAsFile({ @@ -252,7 +218,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onExportDrafts(activityIds?: string[]) { + protected onExportDrafts(activityIds?: string[]) { this.dataService .fetchExport({ activityIds }) .pipe(takeUntilDestroyed(this.destroyRef)) @@ -270,7 +236,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onImport() { + protected onImport() { const dialogRef = this.dialog.open< GfImportActivitiesDialogComponent, ImportActivitiesDialogParams @@ -298,7 +264,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onImportDividends() { + protected onImportDividends() { const dialogRef = this.dialog.open< GfImportActivitiesDialogComponent, ImportActivitiesDialogParams @@ -327,7 +293,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - public onSortChanged({ active, direction }: Sort) { + protected onSortChanged({ active, direction }: Sort) { this.pageIndex = 0; this.sortColumn = active; this.sortDirection = direction; @@ -335,20 +301,56 @@ export class GfActivitiesPageComponent implements OnInit { this.fetchActivities(); } - public onTypesFilterChanged(aTypes: string[]) { + protected onTypesFilterChanged(aTypes: string[]) { this.activityTypesFilter = aTypes; this.pageIndex = 0; this.fetchActivities(); } - public onUpdateActivity(aActivity: Activity) { + protected onUpdateActivity(aActivity: Activity) { this.router.navigate([], { queryParams: { activityId: aActivity.id, editDialog: true } }); } - public openUpdateActivityDialog(aActivity: Activity) { + private fetchActivities() { + // Reset dataSource and totalItems to show loading state + this.dataSource = undefined; + this.totalItems = undefined; + + const dateRange = this.user?.settings?.dateRange; + const range = this.isCalendarYear(dateRange) ? dateRange : undefined; + + this.dataService + .fetchActivities({ + range, + activityTypes: this.activityTypesFilter.length + ? this.activityTypesFilter + : undefined, + filters: this.userService.getFilters(), + skip: this.pageIndex * this.pageSize, + sortColumn: this.sortColumn, + sortDirection: this.sortDirection, + take: this.pageSize + }) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(({ activities, count }) => { + this.dataSource = new MatTableDataSource(activities); + this.totalItems = count; + + if ( + this.hasPermissionToCreateActivity && + this.user?.activitiesCount === 0 + ) { + this.router.navigate([], { queryParams: { createDialog: true } }); + } + + this.changeDetectorRef.markForCheck(); + }); + } + + private openUpdateActivityDialog(aActivity: Activity) { const dialogRef = this.dialog.open< GfCreateOrUpdateActivityDialogComponent, CreateOrUpdateActivityDialogParams @@ -383,7 +385,7 @@ export class GfActivitiesPageComponent implements OnInit { }); } - private isCalendarYear(dateRange: DateRange) { + private isCalendarYear(dateRange?: DateRange) { if (!dateRange) { return false; } @@ -410,11 +412,12 @@ export class GfActivitiesPageComponent implements OnInit { date: new Date(), id: null, fee: 0, + SymbolProfile: null, type: aActivity?.type ?? 'BUY', unitPrice: null }, user: this.user - }, + } satisfies CreateOrUpdateActivityDialogParams, height: this.deviceType === 'mobile' ? '98vh' : '80vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); 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 4dbf6ad9f..022a8f2a2 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 @@ -536,7 +536,13 @@ export class GfCreateOrUpdateActivityDialogComponent { this.dialogRef.close(activity); } else { - (activity as UpdateOrderDto).id = this.data.activity?.id; + const activityId = this.data.activity?.id; + + if (!activityId) { + throw new Error('Activity ID is required for update'); + } + + (activity as UpdateOrderDto).id = activityId; await validateObjectForForm({ classDto: UpdateOrderDto, diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts index 322bcc076..37faff91f 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts @@ -4,8 +4,10 @@ import { Account } from '@prisma/client'; export interface CreateOrUpdateActivityDialogParams { accounts: Account[]; - activity: Omit & { + activity: Partial> & { + id: string | null; SymbolProfile: Activity['SymbolProfile'] | null; + unitPrice: number | null; }; user: User; } diff --git a/apps/client/src/app/pages/portfolio/activities/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/interfaces/interfaces.ts new file mode 100644 index 000000000..51f240cb5 --- /dev/null +++ b/apps/client/src/app/pages/portfolio/activities/interfaces/interfaces.ts @@ -0,0 +1,7 @@ +import { Params } from '@angular/router'; + +export interface ActivitiesPageParams extends Params { + activityId?: string; + createDialog?: string; + editDialog?: string; +} diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json index d207f5966..ae0aaf61a 100644 --- a/apps/client/tsconfig.json +++ b/apps/client/tsconfig.json @@ -22,6 +22,7 @@ "compilerOptions": { "lib": ["dom", "es2022"], "module": "preserve", + "strictNullChecks": true, "target": "es2020" } } diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index d69e5a13a..16a66ff31 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -713,7 +713,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { return { routerLink, mode: SearchMode.QUICK_LINK as const, - name: title + name: title ?? '' }; }); } From de975a59912576b62bbba5993d26dbcd8d794662 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Mon, 13 Jul 2026 01:39:16 -0600 Subject: [PATCH 57/71] Task/set change detection strategy to OnPush in X-ray page (#7313) * Set change detection strategy to OnPush * Update changelog --- CHANGELOG.md | 6 ++++++ .../pages/portfolio/x-ray/x-ray-page.component.ts | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e6752795..83146cce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Changed + +- Set the change detection strategy to `OnPush` in the _X-ray_ page + ## 3.25.0 - 2026-07-12 ### Changed diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts index c5c4fc979..6e162e11f 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -12,7 +12,12 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; @@ -24,6 +29,7 @@ import { import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfPremiumIndicatorComponent, GfRulesComponent, @@ -63,6 +69,8 @@ export class GfXRayPageComponent { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; + + this.changeDetectorRef.markForCheck(); }); this.userService.stateChanged @@ -103,6 +111,8 @@ export class GfXRayPageComponent { private initializePortfolioReport() { this.isLoading = true; + this.changeDetectorRef.markForCheck(); + this.dataService .fetchPortfolioReport() .pipe(takeUntilDestroyed(this.destroyRef)) From 5284f5ec77a6548dbd76c2503b418b9067577d88 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:36:18 +0200 Subject: [PATCH 58/71] Task/harden validation of countries in asset profile endpoints (#7316) * Harden validation of countries * Update changelog --- CHANGELOG.md | 1 + libs/common/src/lib/dtos/country.dto.ts | 11 +++++++++++ libs/common/src/lib/dtos/create-asset-profile.dto.ts | 8 +++++++- libs/common/src/lib/dtos/index.ts | 2 ++ libs/common/src/lib/dtos/update-asset-profile.dto.ts | 8 +++++++- 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 libs/common/src/lib/dtos/country.dto.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 83146cce0..270ea6f09 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 +- Hardened the validation of the countries in the asset profile endpoints - Set the change detection strategy to `OnPush` in the _X-ray_ page ## 3.25.0 - 2026-07-12 diff --git a/libs/common/src/lib/dtos/country.dto.ts b/libs/common/src/lib/dtos/country.dto.ts new file mode 100644 index 000000000..663aa1ac6 --- /dev/null +++ b/libs/common/src/lib/dtos/country.dto.ts @@ -0,0 +1,11 @@ +import { IsISO31661Alpha2, IsNumber, Max, Min } from 'class-validator'; + +export class CountryDto { + @IsISO31661Alpha2() + code: string; + + @IsNumber() + @Min(0) + @Max(1) + weight: number; +} diff --git a/libs/common/src/lib/dtos/create-asset-profile.dto.ts b/libs/common/src/lib/dtos/create-asset-profile.dto.ts index 73f0d3f5a..3e7e2224e 100644 --- a/libs/common/src/lib/dtos/create-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/create-asset-profile.dto.ts @@ -1,15 +1,19 @@ import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; +import { Type } from 'class-transformer'; import { IsArray, IsBoolean, IsEnum, IsOptional, IsString, - IsUrl + IsUrl, + ValidateNested } from 'class-validator'; +import { CountryDto } from './country.dto'; + export class CreateAssetProfileDto { @IsEnum(AssetClass) @IsOptional() @@ -25,6 +29,8 @@ export class CreateAssetProfileDto { @IsArray() @IsOptional() + @Type(() => CountryDto) + @ValidateNested({ each: true }) countries?: Prisma.InputJsonArray; @IsCurrencyCode() diff --git a/libs/common/src/lib/dtos/index.ts b/libs/common/src/lib/dtos/index.ts index cf0ce6f57..dea092398 100644 --- a/libs/common/src/lib/dtos/index.ts +++ b/libs/common/src/lib/dtos/index.ts @@ -1,4 +1,5 @@ import { AuthDeviceDto } from './auth-device.dto'; +import { CountryDto } from './country.dto'; import { CreateAccessDto } from './create-access.dto'; import { CreateAccountBalanceDto } from './create-account-balance.dto'; import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto'; @@ -26,6 +27,7 @@ import { UpdateUserSettingDto } from './update-user-setting.dto'; export { AuthDeviceDto, + CountryDto, CreateAccessDto, CreateAccountBalanceDto, CreateAccountDto, diff --git a/libs/common/src/lib/dtos/update-asset-profile.dto.ts b/libs/common/src/lib/dtos/update-asset-profile.dto.ts index 7096023b3..dc660a481 100644 --- a/libs/common/src/lib/dtos/update-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/update-asset-profile.dto.ts @@ -7,6 +7,7 @@ import { DataSource, Prisma } from '@prisma/client'; +import { Type } from 'class-transformer'; import { IsArray, IsBoolean, @@ -14,9 +15,12 @@ import { IsObject, IsOptional, IsString, - IsUrl + IsUrl, + ValidateNested } from 'class-validator'; +import { CountryDto } from './country.dto'; + export class UpdateAssetProfileDto { @IsEnum(AssetClass) @IsOptional() @@ -32,6 +36,8 @@ export class UpdateAssetProfileDto { @IsArray() @IsOptional() + @Type(() => CountryDto) + @ValidateNested({ each: true }) countries?: Prisma.InputJsonArray; @IsCurrencyCode() From 6c948d39eb62ba06439d585aeec7bfa1974f435b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:50:46 +0200 Subject: [PATCH 59/71] Task/round Fear & Greed index in Twitter bot service (#7318) * Round value * Update changelog --- CHANGELOG.md | 1 + apps/api/src/services/twitter-bot/twitter-bot.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 270ea6f09..c2b310916 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 - Hardened the validation of the countries in the asset profile endpoints +- Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page ## 3.25.0 - 2026-07-12 diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts index 8dffddf7b..2dbed82d2 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts @@ -59,9 +59,9 @@ export class TwitterBotService implements OnModuleInit { symbolItem.marketPrice ); - let status = `Current market mood is ${emoji} ${text.toLowerCase()} (${ + let status = `Current market mood is ${emoji} ${text.toLowerCase()} (${round( symbolItem.marketPrice - }/100)`; + )}/100)`; const benchmarkListing = await this.getBenchmarkListing(); From b7fc13e20a93c07020bbaadfd2e0efad65370e58 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:01:56 +0200 Subject: [PATCH 60/71] Task/harden validation of sectors in asset profile endpoints (#7319) * Harden validation of sectors * Update changelog --- CHANGELOG.md | 1 + libs/common/src/lib/dtos/country.dto.ts | 2 +- libs/common/src/lib/dtos/create-asset-profile.dto.ts | 3 +++ libs/common/src/lib/dtos/index.ts | 2 ++ libs/common/src/lib/dtos/sector.dto.ts | 11 +++++++++++ libs/common/src/lib/dtos/update-asset-profile.dto.ts | 3 +++ 6 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/lib/dtos/sector.dto.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b310916..cdf27bf9b 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 - Hardened the validation of the countries in the asset profile endpoints +- Hardened the validation of the sectors in the asset profile endpoints - Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page diff --git a/libs/common/src/lib/dtos/country.dto.ts b/libs/common/src/lib/dtos/country.dto.ts index 663aa1ac6..7fc8c38ab 100644 --- a/libs/common/src/lib/dtos/country.dto.ts +++ b/libs/common/src/lib/dtos/country.dto.ts @@ -5,7 +5,7 @@ export class CountryDto { code: string; @IsNumber() - @Min(0) @Max(1) + @Min(0) weight: number; } diff --git a/libs/common/src/lib/dtos/create-asset-profile.dto.ts b/libs/common/src/lib/dtos/create-asset-profile.dto.ts index 3e7e2224e..0b0ccb9d3 100644 --- a/libs/common/src/lib/dtos/create-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/create-asset-profile.dto.ts @@ -13,6 +13,7 @@ import { } from 'class-validator'; import { CountryDto } from './country.dto'; +import { SectorDto } from './sector.dto'; export class CreateAssetProfileDto { @IsEnum(AssetClass) @@ -73,6 +74,8 @@ export class CreateAssetProfileDto { @IsArray() @IsOptional() + @Type(() => SectorDto) + @ValidateNested({ each: true }) sectors?: Prisma.InputJsonArray; @IsString() diff --git a/libs/common/src/lib/dtos/index.ts b/libs/common/src/lib/dtos/index.ts index dea092398..c74c68dfd 100644 --- a/libs/common/src/lib/dtos/index.ts +++ b/libs/common/src/lib/dtos/index.ts @@ -11,6 +11,7 @@ import { CreatePlatformDto } from './create-platform.dto'; import { CreateTagDto } from './create-tag.dto'; import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; import { DeleteOwnUserDto } from './delete-own-user.dto'; +import { SectorDto } from './sector.dto'; import { TransferBalanceDto } from './transfer-balance.dto'; import { UpdateAccessDto } from './update-access.dto'; import { UpdateAccountDto } from './update-account.dto'; @@ -39,6 +40,7 @@ export { CreateTagDto, CreateWatchlistItemDto, DeleteOwnUserDto, + SectorDto, TransferBalanceDto, UpdateAccessDto, UpdateAccountDto, diff --git a/libs/common/src/lib/dtos/sector.dto.ts b/libs/common/src/lib/dtos/sector.dto.ts new file mode 100644 index 000000000..e4655cb73 --- /dev/null +++ b/libs/common/src/lib/dtos/sector.dto.ts @@ -0,0 +1,11 @@ +import { IsNumber, IsString, Max, Min } from 'class-validator'; + +export class SectorDto { + @IsString() + name: string; + + @IsNumber() + @Max(1) + @Min(0) + weight: number; +} diff --git a/libs/common/src/lib/dtos/update-asset-profile.dto.ts b/libs/common/src/lib/dtos/update-asset-profile.dto.ts index dc660a481..5b193ff7e 100644 --- a/libs/common/src/lib/dtos/update-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/update-asset-profile.dto.ts @@ -20,6 +20,7 @@ import { } from 'class-validator'; import { CountryDto } from './country.dto'; +import { SectorDto } from './sector.dto'; export class UpdateAssetProfileDto { @IsEnum(AssetClass) @@ -70,6 +71,8 @@ export class UpdateAssetProfileDto { @IsArray() @IsOptional() + @Type(() => SectorDto) + @ValidateNested({ each: true }) sectors?: Prisma.InputJsonArray; @IsOptional() From d5fb6311c14cb6e18001fbcf62eef79a587c8e84 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:05:16 +0200 Subject: [PATCH 61/71] Task/deprecate SymbolProfile in favor of assetProfile in activity interface (#7315) * Deprecate SymbolProfile in favor of assetProfile * Update changelog --- CHANGELOG.md | 1 + .../src/app/activities/activities.service.ts | 18 + .../app/endpoints/public/public.controller.ts | 7 +- apps/api/src/app/export/export.service.ts | 12 +- apps/api/src/app/import/import.service.ts | 38 +- .../portfolio-calculator-test-utils.ts | 2 +- .../calculator/portfolio-calculator.ts | 4 +- ...tfolio-calculator-baln-buy-and-buy.spec.ts | 26 +- ...aln-buy-and-sell-in-two-activities.spec.ts | 462 +++++++-------- ...folio-calculator-baln-buy-and-sell.spec.ts | 26 +- .../portfolio-calculator-baln-buy.spec.ts | 38 +- ...ulator-btceur-in-base-currency-eur.spec.ts | 14 +- .../roai/portfolio-calculator-btceur.spec.ts | 14 +- ...ator-btcusd-buy-and-sell-partially.spec.ts | 26 +- .../portfolio-calculator-btcusd-short.spec.ts | 12 +- .../roai/portfolio-calculator-btcusd.spec.ts | 12 +- .../roai/portfolio-calculator-fee.spec.ts | 14 +- .../portfolio-calculator-googl-buy.spec.ts | 14 +- ...jnug-buy-and-sell-and-buy-and-sell.spec.ts | 380 ++++++------- .../portfolio-calculator-liability.spec.ts | 14 +- ...folio-calculator-msft-buy-and-sell.spec.ts | 38 +- ...-calculator-msft-buy-with-dividend.spec.ts | 26 +- ...ulator-novn-buy-and-sell-partially.spec.ts | 12 +- ...folio-calculator-novn-buy-and-sell.spec.ts | 528 +++++++++--------- .../portfolio-calculator-valuable.spec.ts | 14 +- .../interfaces/portfolio-order.interface.ts | 2 +- .../app/portfolio/portfolio.service.spec.ts | 6 +- .../src/app/portfolio/portfolio.service.ts | 18 +- ...orm-data-source-in-response.interceptor.ts | 7 + .../activities/activities-page.component.ts | 2 +- ...ate-or-update-activity-dialog.component.ts | 30 +- .../interfaces/interfaces.ts | 4 +- .../app/services/import-activities.service.ts | 8 +- libs/common/src/lib/config.ts | 12 + .../lib/interfaces/activities.interface.ts | 8 +- .../public-portfolio-response.interface.ts | 6 + .../activities-table.component.html | 16 +- .../activities-table.component.stories.ts | 10 +- .../activities-table.component.ts | 4 +- 39 files changed, 966 insertions(+), 919 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf27bf9b..9011a3350 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 - Hardened the validation of the sectors in the asset profile endpoints - Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page +- Deprecated `SymbolProfile` in favor of `assetProfile` in the activity interface ## 3.25.0 - 2026-07-12 diff --git a/apps/api/src/app/activities/activities.service.ts b/apps/api/src/app/activities/activities.service.ts index bd08d05f9..427cddf4c 100644 --- a/apps/api/src/app/activities/activities.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -455,6 +455,23 @@ export class ActivitiesService { userId, accountId: account.id, accountUserId: account.userId, + assetProfile: { + activitiesCount: 0, + assetClass: AssetClass.LIQUIDITY, + assetSubClass: AssetSubClass.CASH, + countries: [], + createdAt: new Date(balanceItem.date), + currency: account.currency, + dataSource: + this.dataProviderService.getDataSourceForExchangeRates(), + holdings: [], + id: account.currency, + isActive: true, + name: account.currency, + sectors: [], + symbol: account.currency, + updatedAt: new Date(balanceItem.date) + }, comment: account.name, createdAt: new Date(balanceItem.date), currency: account.currency, @@ -823,6 +840,7 @@ export class ActivitiesService { return { ...order, + assetProfile, feeInAssetProfileCurrency, feeInBaseCurrency, unitPriceInAssetProfileCurrency, diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index f43e51f7b..a87d36218 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -110,26 +110,27 @@ export class PublicController { ? [] : activities.map( ({ + assetProfile, currency, date, fee, quantity, - SymbolProfile, type, unitPrice, value, valueInBaseCurrency }) => { return { + assetProfile, currency, date, fee, quantity, - SymbolProfile, type, unitPrice, value, - valueInBaseCurrency + valueInBaseCurrency, + SymbolProfile: assetProfile }; } ); diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 8ebfde13d..a94479562 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -124,8 +124,8 @@ export class ExportService { const customAssetProfiles = uniqBy( activities - .map(({ SymbolProfile }) => { - return SymbolProfile; + .map(({ assetProfile }) => { + return assetProfile; }) .filter(({ userId: assetProfileUserId }) => { return assetProfileUserId === userId; @@ -224,13 +224,13 @@ export class ExportService { activities: activities.map( ({ accountId, + assetProfile, comment, currency, date, fee, id, quantity, - SymbolProfile, tags: currentTags, type, unitPrice @@ -243,10 +243,10 @@ export class ExportService { quantity, type, unitPrice, - currency: currency ?? SymbolProfile.currency, - dataSource: SymbolProfile.dataSource, + currency: currency ?? assetProfile.currency, + dataSource: assetProfile.dataSource, date: date.toISOString(), - symbol: SymbolProfile.symbol, + symbol: assetProfile.symbol, tags: currentTags.map(({ id: tagId }) => { return tagId; }) diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 9b1aa417c..07019e694 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -122,11 +122,11 @@ export class ImportService { const isDuplicate = activities.some((activity) => { return ( activity.accountId === account?.id && - activity.SymbolProfile.currency === assetProfile.currency && - activity.SymbolProfile.dataSource === assetProfile.dataSource && + activity.assetProfile.currency === assetProfile.currency && + activity.assetProfile.dataSource === assetProfile.dataSource && isSameSecond(activity.date, date) && activity.quantity === quantity && - activity.SymbolProfile.symbol === assetProfile.symbol && + activity.assetProfile.symbol === assetProfile.symbol && activity.type === 'DIVIDEND' && activity.unitPrice === marketPrice ); @@ -138,6 +138,7 @@ export class ImportService { return { account, + assetProfile, date, error, quantity, @@ -463,19 +464,18 @@ export class ImportService { const error = activity.error; const fee = activity.fee; const quantity = activity.quantity; - const SymbolProfile = activity.SymbolProfile; const tagIds = activity.tagIds ?? []; const type = activity.type; const unitPrice = activity.unitPrice; const assetProfile = assetProfiles[ getAssetProfileIdentifier({ - dataSource: SymbolProfile.dataSource, - symbol: SymbolProfile.symbol + dataSource: activity.assetProfile.dataSource, + symbol: activity.assetProfile.symbol }) ] ?? { - dataSource: SymbolProfile.dataSource, - symbol: SymbolProfile.symbol + dataSource: activity.assetProfile.dataSource, + symbol: activity.assetProfile.symbol }; const { assetClass, @@ -619,6 +619,8 @@ export class ImportService { activities.push({ ...order, + // @ts-ignore + assetProfile, error, value, valueInBaseCurrency, @@ -633,19 +635,19 @@ export class ImportService { if (!isDryRun) { // Gather symbol data in the background, if not dry run - const uniqueActivities = uniqBy(activities, ({ SymbolProfile }) => { + const uniqueActivities = uniqBy(activities, ({ assetProfile }) => { return getAssetProfileIdentifier({ - dataSource: SymbolProfile.dataSource, - symbol: SymbolProfile.symbol + dataSource: assetProfile.dataSource, + symbol: assetProfile.symbol }); }); this.dataGatheringService.gatherSymbols({ - dataGatheringItems: uniqueActivities.map(({ date, SymbolProfile }) => { + dataGatheringItems: uniqueActivities.map(({ assetProfile, date }) => { return { date, - dataSource: SymbolProfile.dataSource, - symbol: SymbolProfile.symbol + dataSource: assetProfile.dataSource, + symbol: assetProfile.symbol }; }), priority: DATA_GATHERING_QUEUE_PRIORITY_HIGH @@ -692,12 +694,12 @@ export class ImportService { activity.accountId === accountId && activity.comment === comment && (activity.currency === currency || - activity.SymbolProfile.currency === currency) && - activity.SymbolProfile.dataSource === dataSource && + activity.assetProfile.currency === currency) && + activity.assetProfile.dataSource === dataSource && isSameSecond(activity.date, date) && activity.fee === fee && activity.quantity === quantity && - activity.SymbolProfile.symbol === symbol && + activity.assetProfile.symbol === symbol && activity.type === type && activity.unitPrice === unitPrice ); @@ -717,7 +719,7 @@ export class ImportService { quantity, type, unitPrice, - SymbolProfile: { + assetProfile: { dataSource, symbol, activitiesCount: undefined, diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts index f4c99916f..5e6bfba99 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts @@ -22,7 +22,7 @@ export const activityDummyData = { valueInBaseCurrency: undefined }; -export const symbolProfileDummyData = { +export const assetProfileDummyData = { activitiesCount: undefined, assetClass: undefined, assetSubClass: undefined, diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index e73e222ac..cee94f020 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -121,11 +121,11 @@ export abstract class PortfolioCalculator { this.activities = activities .map( ({ + assetProfile, date, feeInAssetProfileCurrency, feeInBaseCurrency, quantity, - SymbolProfile, tags = [], type, unitPriceInAssetProfileCurrency @@ -141,9 +141,9 @@ export abstract class PortfolioCalculator { } return { + assetProfile, tags, type, - assetProfile: SymbolProfile, date: format(date, DATE_FORMAT), fee: new Big(feeInAssetProfileCurrency), feeInBaseCurrency: new Big(feeInBaseCurrency), diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index f21418cb4..fe23af04b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,33 +85,33 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-11-22'), - feeInAssetProfileCurrency: 1.55, - feeInBaseCurrency: 1.55, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-22'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 142.9 }, { ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 1.65, - feeInBaseCurrency: 1.65, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.65, + feeInBaseCurrency: 1.65, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 136.6 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index e2cba2e61..061aaf817 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -1,231 +1,231 @@ -import { - activityDummyData, - symbolProfileDummyData, - userDummyData -} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; -import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; -import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; -import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; -import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; -import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; -import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; -import { parseDate } from '@ghostfolio/common/helper'; -import { Activity } from '@ghostfolio/common/interfaces'; -import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; - -import { Big } from 'big.js'; - -jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { - return { - CurrentRateService: jest.fn().mockImplementation(() => { - return CurrentRateServiceMock; - }) - }; -}); - -jest.mock( - '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', - () => { - return { - PortfolioSnapshotService: jest.fn().mockImplementation(() => { - return PortfolioSnapshotServiceMock; - }) - }; - } -); - -jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { - return { - RedisCacheService: jest.fn().mockImplementation(() => { - return RedisCacheServiceMock; - }) - }; -}); - -describe('PortfolioCalculator', () => { - let configurationService: ConfigurationService; - let currentRateService: CurrentRateService; - let exchangeRateDataService: ExchangeRateDataService; - let portfolioCalculatorFactory: PortfolioCalculatorFactory; - let portfolioSnapshotService: PortfolioSnapshotService; - let redisCacheService: RedisCacheService; - - beforeEach(() => { - configurationService = new ConfigurationService(); - - currentRateService = new CurrentRateService(null, null, null, null); - - exchangeRateDataService = new ExchangeRateDataService( - null, - null, - null, - null - ); - - portfolioSnapshotService = new PortfolioSnapshotService(null, null); - - redisCacheService = new RedisCacheService(null, null); - - portfolioCalculatorFactory = new PortfolioCalculatorFactory( - configurationService, - currentRateService, - exchangeRateDataService, - portfolioSnapshotService, - redisCacheService - ); - }); - - describe('get current positions', () => { - it.only('with BALN.SW buy and sell in two activities', async () => { - jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime()); - - const activities: Activity[] = [ - { - ...activityDummyData, - date: new Date('2021-11-22'), - feeInAssetProfileCurrency: 1.55, - feeInBaseCurrency: 1.55, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'CHF', - dataSource: 'YAHOO', - name: 'Bâloise Holding AG', - symbol: 'BALN.SW' - }, - type: 'BUY', - unitPriceInAssetProfileCurrency: 142.9 - }, - { - ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 1.65, - feeInBaseCurrency: 1.65, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'CHF', - dataSource: 'YAHOO', - name: 'Bâloise Holding AG', - symbol: 'BALN.SW' - }, - type: 'SELL', - unitPriceInAssetProfileCurrency: 136.6 - }, - { - ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'CHF', - dataSource: 'YAHOO', - name: 'Bâloise Holding AG', - symbol: 'BALN.SW' - }, - type: 'SELL', - unitPriceInAssetProfileCurrency: 136.6 - } - ]; - - const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ - activities, - calculationType: PerformanceCalculationType.ROAI, - currency: 'CHF', - userId: userDummyData.id - }); - - const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - - const investments = portfolioCalculator.getInvestments(); - - const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ - data: portfolioSnapshot.historicalData, - groupBy: 'month' - }); - - const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ - data: portfolioSnapshot.historicalData, - groupBy: 'year' - }); - - expect(portfolioSnapshot).toMatchObject({ - currentValueInBaseCurrency: new Big('0'), - errors: [], - hasErrors: false, - positions: [ - { - activitiesCount: 3, - averagePrice: new Big('0'), - currency: 'CHF', - dataSource: 'YAHOO', - dateOfFirstActivity: '2021-11-22', - dividend: new Big('0'), - dividendInBaseCurrency: new Big('0'), - fee: new Big('3.2'), - feeInBaseCurrency: new Big('3.2'), - grossPerformance: new Big('-12.6'), - grossPerformancePercentage: new Big('-0.04408677396780965649'), - grossPerformancePercentageWithCurrencyEffect: new Big( - '-0.04408677396780965649' - ), - grossPerformanceWithCurrencyEffect: new Big('-12.6'), - investment: new Big('0'), - investmentWithCurrencyEffect: new Big('0'), - netPerformancePercentageWithCurrencyEffectMap: { - max: new Big('-0.0552834149755073478') - }, - netPerformanceWithCurrencyEffectMap: { - max: new Big('-15.8') - }, - marketPrice: 148.9, - marketPriceInBaseCurrency: 148.9, - quantity: new Big('0'), - symbol: 'BALN.SW', - tags: [], - timeWeightedInvestment: new Big('285.80000000000000396627'), - timeWeightedInvestmentWithCurrencyEffect: new Big( - '285.80000000000000396627' - ), - valueInBaseCurrency: new Big('0') - } - ], - totalFeesWithCurrencyEffect: new Big('3.2'), - totalInterestWithCurrencyEffect: new Big('0'), - totalInvestment: new Big('0'), - totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0') - }); - - expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( - expect.objectContaining({ - netPerformance: -15.8, - netPerformanceInPercentage: -0.05528341497550734703, - netPerformanceInPercentageWithCurrencyEffect: -0.05528341497550734703, - netPerformanceWithCurrencyEffect: -15.8, - totalInvestment: 0, - totalInvestmentValueWithCurrencyEffect: 0 - }) - ); - - expect(investments).toEqual([ - { date: '2021-11-22', investment: new Big('285.8') }, - { date: '2021-11-30', investment: new Big('0') } - ]); - - expect(investmentsByMonth).toEqual([ - { date: '2021-11-01', investment: 0 }, - { date: '2021-12-01', investment: 0 } - ]); - - expect(investmentsByYear).toEqual([ - { date: '2021-01-01', investment: 0 } - ]); - }); - }); -}); +import { + activityDummyData, + assetProfileDummyData, + userDummyData +} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; +import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; +import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { Big } from 'big.js'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', + () => { + return { + PortfolioSnapshotService: jest.fn().mockImplementation(() => { + return PortfolioSnapshotServiceMock; + }) + }; + } +); + +jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { + return { + RedisCacheService: jest.fn().mockImplementation(() => { + return RedisCacheServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let exchangeRateDataService: ExchangeRateDataService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeEach(() => { + configurationService = new ConfigurationService(); + + currentRateService = new CurrentRateService(null, null, null, null); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null, null); + + redisCacheService = new RedisCacheService(null, null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('get current positions', () => { + it.only('with BALN.SW buy and sell in two activities', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime()); + + const activities: Activity[] = [ + { + ...activityDummyData, + assetProfile: { + ...assetProfileDummyData, + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, + date: new Date('2021-11-22'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, + type: 'BUY', + unitPriceInAssetProfileCurrency: 142.9 + }, + { + ...activityDummyData, + assetProfile: { + ...assetProfileDummyData, + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.65, + feeInBaseCurrency: 1.65, + quantity: 1, + type: 'SELL', + unitPriceInAssetProfileCurrency: 136.6 + }, + { + ...activityDummyData, + assetProfile: { + ...assetProfileDummyData, + currency: 'CHF', + dataSource: 'YAHOO', + name: 'Bâloise Holding AG', + symbol: 'BALN.SW' + }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, + type: 'SELL', + unitPriceInAssetProfileCurrency: 136.6 + } + ]; + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'CHF', + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + const investments = portfolioCalculator.getInvestments(); + + const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'month' + }); + + const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'year' + }); + + expect(portfolioSnapshot).toMatchObject({ + currentValueInBaseCurrency: new Big('0'), + errors: [], + hasErrors: false, + positions: [ + { + activitiesCount: 3, + averagePrice: new Big('0'), + currency: 'CHF', + dataSource: 'YAHOO', + dateOfFirstActivity: '2021-11-22', + dividend: new Big('0'), + dividendInBaseCurrency: new Big('0'), + fee: new Big('3.2'), + feeInBaseCurrency: new Big('3.2'), + grossPerformance: new Big('-12.6'), + grossPerformancePercentage: new Big('-0.04408677396780965649'), + grossPerformancePercentageWithCurrencyEffect: new Big( + '-0.04408677396780965649' + ), + grossPerformanceWithCurrencyEffect: new Big('-12.6'), + investment: new Big('0'), + investmentWithCurrencyEffect: new Big('0'), + netPerformancePercentageWithCurrencyEffectMap: { + max: new Big('-0.0552834149755073478') + }, + netPerformanceWithCurrencyEffectMap: { + max: new Big('-15.8') + }, + marketPrice: 148.9, + marketPriceInBaseCurrency: 148.9, + quantity: new Big('0'), + symbol: 'BALN.SW', + tags: [], + timeWeightedInvestment: new Big('285.80000000000000396627'), + timeWeightedInvestmentWithCurrencyEffect: new Big( + '285.80000000000000396627' + ), + valueInBaseCurrency: new Big('0') + } + ], + totalFeesWithCurrencyEffect: new Big('3.2'), + totalInterestWithCurrencyEffect: new Big('0'), + totalInvestment: new Big('0'), + totalInvestmentWithCurrencyEffect: new Big('0'), + totalLiabilitiesWithCurrencyEffect: new Big('0') + }); + + expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( + expect.objectContaining({ + netPerformance: -15.8, + netPerformanceInPercentage: -0.05528341497550734703, + netPerformanceInPercentageWithCurrencyEffect: -0.05528341497550734703, + netPerformanceWithCurrencyEffect: -15.8, + totalInvestment: 0, + totalInvestmentValueWithCurrencyEffect: 0 + }) + ); + + expect(investments).toEqual([ + { date: '2021-11-22', investment: new Big('285.8') }, + { date: '2021-11-30', investment: new Big('0') } + ]); + + expect(investmentsByMonth).toEqual([ + { date: '2021-11-01', investment: 0 }, + { date: '2021-12-01', investment: 0 } + ]); + + expect(investmentsByYear).toEqual([ + { date: '2021-01-01', investment: 0 } + ]); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index fa397de46..c8e0af46d 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,33 +85,33 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-11-22'), - feeInAssetProfileCurrency: 1.55, - feeInBaseCurrency: 1.55, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-22'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 142.9 }, { ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 1.65, - feeInBaseCurrency: 1.65, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.65, + feeInBaseCurrency: 1.65, + quantity: 2, type: 'SELL', unitPriceInAssetProfileCurrency: 136.6 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index db44fe0ae..4922382a9 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,17 +85,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 1.55, - feeInBaseCurrency: 1.55, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 136.6 } @@ -217,17 +217,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 1.55, - feeInBaseCurrency: 1.55, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 135.0 } @@ -257,17 +257,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-11-30'), - feeInAssetProfileCurrency: 1.55, - feeInBaseCurrency: 1.55, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'CHF', dataSource: 'YAHOO', name: 'Bâloise Holding AG', symbol: 'BALN.SW' }, + date: new Date('2021-11-30'), + feeInAssetProfileCurrency: 1.55, + feeInBaseCurrency: 1.55, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 135.0 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts index 1afd9225d..21a8d2056 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts @@ -1,7 +1,7 @@ import { activityDummyData, + assetProfileDummyData, loadExportFile, - symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -107,17 +107,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = exportResponse.activities.map( (activity) => ({ ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: 4.46, - feeInBaseCurrency: 3.94, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: activity.dataSource, name: 'Bitcoin', symbol: activity.symbol }, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: 4.46, + feeInBaseCurrency: 3.94, unitPriceInAssetProfileCurrency: 44558.42 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index 0e5750166..bc80e8996 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -1,7 +1,7 @@ import { activityDummyData, + assetProfileDummyData, loadExportFile, - symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -95,17 +95,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = exportResponse.activities.map( (activity) => ({ ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: 4.46, - feeInBaseCurrency: 4.46, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: activity.dataSource, name: 'Bitcoin', symbol: activity.symbol }, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: 4.46, + feeInBaseCurrency: 4.46, unitPriceInAssetProfileCurrency: 44558.42 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index 1e556735d..f20506b06 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -98,33 +98,33 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2015-01-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 2, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Bitcoin USD', symbol: 'BTCUSD' }, + date: new Date('2015-01-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 2, type: 'BUY', unitPriceInAssetProfileCurrency: 320.43 }, { ...activityDummyData, - date: new Date('2017-12-31'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Bitcoin USD', symbol: 'BTCUSD' }, + date: new Date('2017-12-31'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, type: 'SELL', unitPriceInAssetProfileCurrency: 14156.4 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts index 9d84540e3..79c6979ef 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts @@ -1,7 +1,7 @@ import { activityDummyData, + assetProfileDummyData, loadExportFile, - symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -96,16 +96,16 @@ describe('PortfolioCalculator', () => { (activity) => ({ ...activityDummyData, ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - feeInBaseCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: activity.dataSource, name: 'Bitcoin', symbol: activity.symbol }, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + feeInBaseCurrency: activity.fee, unitPriceInAssetProfileCurrency: activity.unitPrice }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index 0916e18a4..18b0e6d64 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -1,7 +1,7 @@ import { activityDummyData, + assetProfileDummyData, loadExportFile, - symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -96,16 +96,16 @@ describe('PortfolioCalculator', () => { (activity) => ({ ...activityDummyData, ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: 4.46, - feeInBaseCurrency: 4.46, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: activity.dataSource, name: 'Bitcoin', symbol: activity.symbol }, + date: parseDate(activity.date), + feeInAssetProfileCurrency: 4.46, + feeInBaseCurrency: 4.46, unitPriceInAssetProfileCurrency: 44558.42 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts index b55c94b2b..e5798818f 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,17 +85,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-09-01'), - feeInAssetProfileCurrency: 49, - feeInBaseCurrency: 49, - quantity: 0, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'MANUAL', name: 'Account Opening Fee', symbol: '2c463fb3-af07-486e-adb0-8301b3d72141' }, + date: new Date('2021-09-01'), + feeInAssetProfileCurrency: 49, + feeInBaseCurrency: 49, + quantity: 0, type: 'FEE', unitPriceInAssetProfileCurrency: 0 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index bb61d637e..984dc1154 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -97,17 +97,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2023-01-03'), - feeInAssetProfileCurrency: 1, - feeInBaseCurrency: 0.9238, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Alphabet Inc.', symbol: 'GOOGL' }, + date: new Date('2023-01-03'), + feeInAssetProfileCurrency: 1, + feeInBaseCurrency: 0.9238, + quantity: 1, type: 'BUY', unitPriceInAssetProfileCurrency: 89.12 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts index 88afc971d..962cfe2d8 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts @@ -1,190 +1,190 @@ -import { - activityDummyData, - loadExportFile, - symbolProfileDummyData, - userDummyData -} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; -import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; -import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; -import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; -import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; -import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; -import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; -import { parseDate } from '@ghostfolio/common/helper'; -import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; -import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; - -import { Big } from 'big.js'; -import { join } from 'node:path'; - -jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { - return { - CurrentRateService: jest.fn().mockImplementation(() => { - return CurrentRateServiceMock; - }) - }; -}); - -jest.mock( - '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', - () => { - return { - PortfolioSnapshotService: jest.fn().mockImplementation(() => { - return PortfolioSnapshotServiceMock; - }) - }; - } -); - -jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { - return { - RedisCacheService: jest.fn().mockImplementation(() => { - return RedisCacheServiceMock; - }) - }; -}); - -describe('PortfolioCalculator', () => { - let exportResponse: ExportResponse; - - let configurationService: ConfigurationService; - let currentRateService: CurrentRateService; - let exchangeRateDataService: ExchangeRateDataService; - let portfolioCalculatorFactory: PortfolioCalculatorFactory; - let portfolioSnapshotService: PortfolioSnapshotService; - let redisCacheService: RedisCacheService; - - beforeAll(() => { - exportResponse = loadExportFile( - join( - __dirname, - '../../../../../../../test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json' - ) - ); - }); - - beforeEach(() => { - configurationService = new ConfigurationService(); - - currentRateService = new CurrentRateService(null, null, null, null); - - exchangeRateDataService = new ExchangeRateDataService( - null, - null, - null, - null - ); - - portfolioSnapshotService = new PortfolioSnapshotService(null, null); - - redisCacheService = new RedisCacheService(null, null); - - portfolioCalculatorFactory = new PortfolioCalculatorFactory( - configurationService, - currentRateService, - exchangeRateDataService, - portfolioSnapshotService, - redisCacheService - ); - }); - - describe('get current positions', () => { - it.only('with JNUG buy and sell', async () => { - jest.useFakeTimers().setSystemTime(parseDate('2025-12-28').getTime()); - - const activities: Activity[] = exportResponse.activities.map( - (activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - feeInBaseCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, - currency: activity.currency, - dataSource: activity.dataSource, - name: 'Direxion Daily Junior Gold Miners Index Bull 2X Shares', - symbol: activity.symbol - }, - unitPriceInAssetProfileCurrency: activity.unitPrice - }) - ); - - const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ - activities, - calculationType: PerformanceCalculationType.ROAI, - currency: exportResponse.user.settings.currency, - userId: userDummyData.id - }); - - const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - - const investments = portfolioCalculator.getInvestments(); - - const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ - data: portfolioSnapshot.historicalData, - groupBy: 'month' - }); - - const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ - data: portfolioSnapshot.historicalData, - groupBy: 'year' - }); - - expect(portfolioSnapshot).toMatchObject({ - currentValueInBaseCurrency: new Big('0'), - errors: [], - hasErrors: false, - positions: [ - { - activitiesCount: 4, - averagePrice: new Big('0'), - currency: 'USD', - dataSource: 'YAHOO', - dateOfFirstActivity: '2025-12-11', - dividend: new Big('0'), - dividendInBaseCurrency: new Big('0'), - fee: new Big('4'), - feeInBaseCurrency: new Big('4'), - grossPerformance: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) - grossPerformanceWithCurrencyEffect: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) - investment: new Big('0'), - investmentWithCurrencyEffect: new Big('0'), - netPerformance: new Big('39.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4 - netPerformanceWithCurrencyEffectMap: { - max: new Big('39.95') // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4 - }, - marketPrice: 237.8000030517578, - marketPriceInBaseCurrency: 237.8000030517578, - quantity: new Big('0'), - symbol: 'JNUG', - tags: [], - valueInBaseCurrency: new Big('0') - } - ], - totalFeesWithCurrencyEffect: new Big('4'), - totalInterestWithCurrencyEffect: new Big('0'), - totalInvestment: new Big('0'), - totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0') - }); - - expect(investments).toEqual([ - { date: '2025-12-11', investment: new Big('1885.05') }, - { date: '2025-12-18', investment: new Big('2041.1') }, - { date: '2025-12-28', investment: new Big('0') } - ]); - - expect(investmentsByMonth).toEqual([ - { date: '2025-12-01', investment: 0 } - ]); - - expect(investmentsByYear).toEqual([ - { date: '2025-01-01', investment: 0 } - ]); - }); - }); -}); +import { + activityDummyData, + assetProfileDummyData, + loadExportFile, + userDummyData +} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; +import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; +import { parseDate } from '@ghostfolio/common/helper'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { Big } from 'big.js'; +import { join } from 'node:path'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', + () => { + return { + PortfolioSnapshotService: jest.fn().mockImplementation(() => { + return PortfolioSnapshotServiceMock; + }) + }; + } +); + +jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { + return { + RedisCacheService: jest.fn().mockImplementation(() => { + return RedisCacheServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let exportResponse: ExportResponse; + + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let exchangeRateDataService: ExchangeRateDataService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeAll(() => { + exportResponse = loadExportFile( + join( + __dirname, + '../../../../../../../test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json' + ) + ); + }); + + beforeEach(() => { + configurationService = new ConfigurationService(); + + currentRateService = new CurrentRateService(null, null, null, null); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null, null); + + redisCacheService = new RedisCacheService(null, null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('get current positions', () => { + it.only('with JNUG buy and sell', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2025-12-28').getTime()); + + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + assetProfile: { + ...assetProfileDummyData, + currency: activity.currency, + dataSource: activity.dataSource, + name: 'Direxion Daily Junior Gold Miners Index Bull 2X Shares', + symbol: activity.symbol + }, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + feeInBaseCurrency: activity.fee, + unitPriceInAssetProfileCurrency: activity.unitPrice + }) + ); + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: exportResponse.user.settings.currency, + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + const investments = portfolioCalculator.getInvestments(); + + const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'month' + }); + + const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'year' + }); + + expect(portfolioSnapshot).toMatchObject({ + currentValueInBaseCurrency: new Big('0'), + errors: [], + hasErrors: false, + positions: [ + { + activitiesCount: 4, + averagePrice: new Big('0'), + currency: 'USD', + dataSource: 'YAHOO', + dateOfFirstActivity: '2025-12-11', + dividend: new Big('0'), + dividendInBaseCurrency: new Big('0'), + fee: new Big('4'), + feeInBaseCurrency: new Big('4'), + grossPerformance: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) + grossPerformanceWithCurrencyEffect: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) + investment: new Big('0'), + investmentWithCurrencyEffect: new Big('0'), + netPerformance: new Big('39.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4 + netPerformanceWithCurrencyEffectMap: { + max: new Big('39.95') // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4 + }, + marketPrice: 237.8000030517578, + marketPriceInBaseCurrency: 237.8000030517578, + quantity: new Big('0'), + symbol: 'JNUG', + tags: [], + valueInBaseCurrency: new Big('0') + } + ], + totalFeesWithCurrencyEffect: new Big('4'), + totalInterestWithCurrencyEffect: new Big('0'), + totalInvestment: new Big('0'), + totalInvestmentWithCurrencyEffect: new Big('0'), + totalLiabilitiesWithCurrencyEffect: new Big('0') + }); + + expect(investments).toEqual([ + { date: '2025-12-11', investment: new Big('1885.05') }, + { date: '2025-12-18', investment: new Big('2041.1') }, + { date: '2025-12-28', investment: new Big('0') } + ]); + + expect(investmentsByMonth).toEqual([ + { date: '2025-12-01', investment: 0 } + ]); + + expect(investmentsByYear).toEqual([ + { date: '2025-01-01', investment: 0 } + ]); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts index 4f2058513..94654af61 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,17 +85,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2023-01-01'), // Date in future - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'MANUAL', name: 'Loan', symbol: '55196015-1365-4560-aa60-8751ae6d18f8' }, + date: new Date('2023-01-01'), // Date in future + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, type: 'LIABILITY', unitPriceInAssetProfileCurrency: 3000 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts index 36d1ea1ec..b1f030aae 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -78,49 +78,49 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2024-03-08'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 0.3333333333333333, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Microsoft Inc.', symbol: 'MSFT' }, + date: new Date('2024-03-08'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 0.3333333333333333, type: 'BUY', unitPriceInAssetProfileCurrency: 408 }, { ...activityDummyData, - date: new Date('2024-03-13'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 0.6666666666666666, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Microsoft Inc.', symbol: 'MSFT' }, + date: new Date('2024-03-13'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 0.6666666666666666, type: 'BUY', unitPriceInAssetProfileCurrency: 400 }, { ...activityDummyData, - date: new Date('2024-03-14'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Microsoft Inc.', symbol: 'MSFT' }, + date: new Date('2024-03-14'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, type: 'SELL', unitPriceInAssetProfileCurrency: 411 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index c50ef2b34..91c095623 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,33 +85,33 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2021-09-16'), - feeInAssetProfileCurrency: 19, - feeInBaseCurrency: 19, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Microsoft Inc.', symbol: 'MSFT' }, + date: new Date('2021-09-16'), + feeInAssetProfileCurrency: 19, + feeInBaseCurrency: 19, + quantity: 1, type: 'BUY', unitPriceInAssetProfileCurrency: 298.58 }, { ...activityDummyData, - date: new Date('2021-11-16'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'YAHOO', name: 'Microsoft Inc.', symbol: 'MSFT' }, + date: new Date('2021-11-16'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, type: 'DIVIDEND', unitPriceInAssetProfileCurrency: 0.62 } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index cdfc78906..8ce62db59 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -1,7 +1,7 @@ import { activityDummyData, + assetProfileDummyData, loadExportFile, - symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -99,16 +99,16 @@ describe('PortfolioCalculator', () => { (activity) => ({ ...activityDummyData, ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - feeInBaseCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: activity.currency, dataSource: activity.dataSource, name: 'Novartis AG', symbol: activity.symbol }, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + feeInBaseCurrency: activity.fee, unitPriceInAssetProfileCurrency: activity.unitPrice }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index 2cd754c82..f86b23d9a 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -1,264 +1,264 @@ -import { - activityDummyData, - loadExportFile, - symbolProfileDummyData, - userDummyData -} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; -import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; -import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; -import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; -import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; -import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; -import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; -import { parseDate } from '@ghostfolio/common/helper'; -import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; -import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; - -import { Big } from 'big.js'; -import { join } from 'node:path'; - -jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { - return { - CurrentRateService: jest.fn().mockImplementation(() => { - return CurrentRateServiceMock; - }) - }; -}); - -jest.mock( - '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', - () => { - return { - PortfolioSnapshotService: jest.fn().mockImplementation(() => { - return PortfolioSnapshotServiceMock; - }) - }; - } -); - -jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { - return { - RedisCacheService: jest.fn().mockImplementation(() => { - return RedisCacheServiceMock; - }) - }; -}); - -describe('PortfolioCalculator', () => { - let exportResponse: ExportResponse; - - let configurationService: ConfigurationService; - let currentRateService: CurrentRateService; - let exchangeRateDataService: ExchangeRateDataService; - let portfolioCalculatorFactory: PortfolioCalculatorFactory; - let portfolioSnapshotService: PortfolioSnapshotService; - let redisCacheService: RedisCacheService; - - beforeAll(() => { - exportResponse = loadExportFile( - join( - __dirname, - '../../../../../../../test/import/ok/novn-buy-and-sell.json' - ) - ); - }); - - beforeEach(() => { - configurationService = new ConfigurationService(); - - currentRateService = new CurrentRateService(null, null, null, null); - - exchangeRateDataService = new ExchangeRateDataService( - null, - null, - null, - null - ); - - portfolioSnapshotService = new PortfolioSnapshotService(null, null); - - redisCacheService = new RedisCacheService(null, null); - - portfolioCalculatorFactory = new PortfolioCalculatorFactory( - configurationService, - currentRateService, - exchangeRateDataService, - portfolioSnapshotService, - redisCacheService - ); - }); - - describe('get current positions', () => { - it.only('with NOVN.SW buy and sell', async () => { - jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime()); - - const activities: Activity[] = exportResponse.activities.map( - (activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - feeInBaseCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, - currency: activity.currency, - dataSource: activity.dataSource, - name: 'Novartis AG', - symbol: activity.symbol - }, - unitPriceInAssetProfileCurrency: activity.unitPrice - }) - ); - - const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ - activities, - calculationType: PerformanceCalculationType.ROAI, - currency: exportResponse.user.settings.currency, - userId: userDummyData.id - }); - - const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - - const investments = portfolioCalculator.getInvestments(); - - const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ - data: portfolioSnapshot.historicalData, - groupBy: 'month' - }); - - const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ - data: portfolioSnapshot.historicalData, - groupBy: 'year' - }); - - expect(portfolioSnapshot.historicalData[0]).toEqual({ - date: '2022-03-06', - investmentValueWithCurrencyEffect: 0, - netPerformance: 0, - netPerformanceInPercentage: 0, - netPerformanceInPercentageWithCurrencyEffect: 0, - netPerformanceWithCurrencyEffect: 0, - netWorth: 0, - totalAccountBalance: 0, - totalInvestment: 0, - totalInvestmentValueWithCurrencyEffect: 0, - value: 0, - valueWithCurrencyEffect: 0 - }); - - /** - * Closing price on 2022-03-07 is unknown, - * hence it uses the last unit price (2022-04-11): 87.8 - */ - expect(portfolioSnapshot.historicalData[1]).toEqual({ - date: '2022-03-07', - investmentValueWithCurrencyEffect: 151.6, - netPerformance: 24, // 2 * (87.8 - 75.8) = 24 - netPerformanceInPercentage: 0.158311345646438, // 24 ÷ 151.6 = 0.158311345646438 - netPerformanceInPercentageWithCurrencyEffect: 0.158311345646438, // 24 ÷ 151.6 = 0.158311345646438 - netPerformanceWithCurrencyEffect: 24, - netWorth: 175.6, // 2 * 87.8 = 175.6 - totalAccountBalance: 0, - totalInvestment: 151.6, - totalInvestmentValueWithCurrencyEffect: 151.6, - value: 175.6, // 2 * 87.8 = 175.6 - valueWithCurrencyEffect: 175.6 - }); - - expect( - portfolioSnapshot.historicalData[ - portfolioSnapshot.historicalData.length - 1 - ] - ).toEqual({ - date: '2022-04-11', - investmentValueWithCurrencyEffect: 0, - netPerformance: 19.86, - netPerformanceInPercentage: 0.13100263852242744, - netPerformanceInPercentageWithCurrencyEffect: 0.13100263852242744, - netPerformanceWithCurrencyEffect: 19.86, - netWorth: 0, - totalAccountBalance: 0, - totalInvestment: 0, - totalInvestmentValueWithCurrencyEffect: 0, - value: 0, - valueWithCurrencyEffect: 0 - }); - - expect(portfolioSnapshot).toMatchObject({ - currentValueInBaseCurrency: new Big('0'), - errors: [], - hasErrors: false, - positions: [ - { - activitiesCount: 2, - averagePrice: new Big('0'), - currency: 'CHF', - dataSource: 'YAHOO', - dateOfFirstActivity: '2022-03-07', - dividend: new Big('0'), - dividendInBaseCurrency: new Big('0'), - fee: new Big('0'), - feeInBaseCurrency: new Big('0'), - grossPerformance: new Big('19.86'), - grossPerformancePercentage: new Big('0.13100263852242744063'), - grossPerformancePercentageWithCurrencyEffect: new Big( - '0.13100263852242744063' - ), - grossPerformanceWithCurrencyEffect: new Big('19.86'), - investment: new Big('0'), - investmentWithCurrencyEffect: new Big('0'), - netPerformance: new Big('19.86'), - netPerformancePercentage: new Big('0.13100263852242744063'), - netPerformancePercentageWithCurrencyEffectMap: { - max: new Big('0.13100263852242744063') - }, - netPerformanceWithCurrencyEffectMap: { - max: new Big('19.86') - }, - marketPrice: 87.8, - marketPriceInBaseCurrency: 87.8, - quantity: new Big('0'), - symbol: 'NOVN.SW', - tags: [], - timeWeightedInvestment: new Big('151.6'), - timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'), - valueInBaseCurrency: new Big('0') - } - ], - totalFeesWithCurrencyEffect: new Big('0'), - totalInterestWithCurrencyEffect: new Big('0'), - totalInvestment: new Big('0'), - totalInvestmentWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0') - }); - - expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( - expect.objectContaining({ - netPerformance: 19.86, - netPerformanceInPercentage: 0.13100263852242744063, - netPerformanceInPercentageWithCurrencyEffect: 0.13100263852242744063, - netPerformanceWithCurrencyEffect: 19.86, - totalInvestment: 0, - totalInvestmentValueWithCurrencyEffect: 0 - }) - ); - - expect(investments).toEqual([ - { date: '2022-03-07', investment: new Big('151.6') }, - { date: '2022-04-08', investment: new Big('0') } - ]); - - expect(investmentsByMonth).toEqual([ - { date: '2022-03-01', investment: 151.6 }, - { date: '2022-04-01', investment: -151.6 } - ]); - - expect(investmentsByYear).toEqual([ - { date: '2022-01-01', investment: 0 } - ]); - }); - }); -}); +import { + activityDummyData, + assetProfileDummyData, + loadExportFile, + userDummyData +} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; +import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; +import { parseDate } from '@ghostfolio/common/helper'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { Big } from 'big.js'; +import { join } from 'node:path'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', + () => { + return { + PortfolioSnapshotService: jest.fn().mockImplementation(() => { + return PortfolioSnapshotServiceMock; + }) + }; + } +); + +jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { + return { + RedisCacheService: jest.fn().mockImplementation(() => { + return RedisCacheServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let exportResponse: ExportResponse; + + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let exchangeRateDataService: ExchangeRateDataService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeAll(() => { + exportResponse = loadExportFile( + join( + __dirname, + '../../../../../../../test/import/ok/novn-buy-and-sell.json' + ) + ); + }); + + beforeEach(() => { + configurationService = new ConfigurationService(); + + currentRateService = new CurrentRateService(null, null, null, null); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null, null); + + redisCacheService = new RedisCacheService(null, null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('get current positions', () => { + it.only('with NOVN.SW buy and sell', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime()); + + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + assetProfile: { + ...assetProfileDummyData, + currency: activity.currency, + dataSource: activity.dataSource, + name: 'Novartis AG', + symbol: activity.symbol + }, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + feeInBaseCurrency: activity.fee, + unitPriceInAssetProfileCurrency: activity.unitPrice + }) + ); + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: exportResponse.user.settings.currency, + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + const investments = portfolioCalculator.getInvestments(); + + const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'month' + }); + + const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'year' + }); + + expect(portfolioSnapshot.historicalData[0]).toEqual({ + date: '2022-03-06', + investmentValueWithCurrencyEffect: 0, + netPerformance: 0, + netPerformanceInPercentage: 0, + netPerformanceInPercentageWithCurrencyEffect: 0, + netPerformanceWithCurrencyEffect: 0, + netWorth: 0, + totalAccountBalance: 0, + totalInvestment: 0, + totalInvestmentValueWithCurrencyEffect: 0, + value: 0, + valueWithCurrencyEffect: 0 + }); + + /** + * Closing price on 2022-03-07 is unknown, + * hence it uses the last unit price (2022-04-11): 87.8 + */ + expect(portfolioSnapshot.historicalData[1]).toEqual({ + date: '2022-03-07', + investmentValueWithCurrencyEffect: 151.6, + netPerformance: 24, // 2 * (87.8 - 75.8) = 24 + netPerformanceInPercentage: 0.158311345646438, // 24 ÷ 151.6 = 0.158311345646438 + netPerformanceInPercentageWithCurrencyEffect: 0.158311345646438, // 24 ÷ 151.6 = 0.158311345646438 + netPerformanceWithCurrencyEffect: 24, + netWorth: 175.6, // 2 * 87.8 = 175.6 + totalAccountBalance: 0, + totalInvestment: 151.6, + totalInvestmentValueWithCurrencyEffect: 151.6, + value: 175.6, // 2 * 87.8 = 175.6 + valueWithCurrencyEffect: 175.6 + }); + + expect( + portfolioSnapshot.historicalData[ + portfolioSnapshot.historicalData.length - 1 + ] + ).toEqual({ + date: '2022-04-11', + investmentValueWithCurrencyEffect: 0, + netPerformance: 19.86, + netPerformanceInPercentage: 0.13100263852242744, + netPerformanceInPercentageWithCurrencyEffect: 0.13100263852242744, + netPerformanceWithCurrencyEffect: 19.86, + netWorth: 0, + totalAccountBalance: 0, + totalInvestment: 0, + totalInvestmentValueWithCurrencyEffect: 0, + value: 0, + valueWithCurrencyEffect: 0 + }); + + expect(portfolioSnapshot).toMatchObject({ + currentValueInBaseCurrency: new Big('0'), + errors: [], + hasErrors: false, + positions: [ + { + activitiesCount: 2, + averagePrice: new Big('0'), + currency: 'CHF', + dataSource: 'YAHOO', + dateOfFirstActivity: '2022-03-07', + dividend: new Big('0'), + dividendInBaseCurrency: new Big('0'), + fee: new Big('0'), + feeInBaseCurrency: new Big('0'), + grossPerformance: new Big('19.86'), + grossPerformancePercentage: new Big('0.13100263852242744063'), + grossPerformancePercentageWithCurrencyEffect: new Big( + '0.13100263852242744063' + ), + grossPerformanceWithCurrencyEffect: new Big('19.86'), + investment: new Big('0'), + investmentWithCurrencyEffect: new Big('0'), + netPerformance: new Big('19.86'), + netPerformancePercentage: new Big('0.13100263852242744063'), + netPerformancePercentageWithCurrencyEffectMap: { + max: new Big('0.13100263852242744063') + }, + netPerformanceWithCurrencyEffectMap: { + max: new Big('19.86') + }, + marketPrice: 87.8, + marketPriceInBaseCurrency: 87.8, + quantity: new Big('0'), + symbol: 'NOVN.SW', + tags: [], + timeWeightedInvestment: new Big('151.6'), + timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'), + valueInBaseCurrency: new Big('0') + } + ], + totalFeesWithCurrencyEffect: new Big('0'), + totalInterestWithCurrencyEffect: new Big('0'), + totalInvestment: new Big('0'), + totalInvestmentWithCurrencyEffect: new Big('0'), + totalLiabilitiesWithCurrencyEffect: new Big('0') + }); + + expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( + expect.objectContaining({ + netPerformance: 19.86, + netPerformanceInPercentage: 0.13100263852242744063, + netPerformanceInPercentageWithCurrencyEffect: 0.13100263852242744063, + netPerformanceWithCurrencyEffect: 19.86, + totalInvestment: 0, + totalInvestmentValueWithCurrencyEffect: 0 + }) + ); + + expect(investments).toEqual([ + { date: '2022-03-07', investment: new Big('151.6') }, + { date: '2022-04-08', investment: new Big('0') } + ]); + + expect(investmentsByMonth).toEqual([ + { date: '2022-03-01', investment: 151.6 }, + { date: '2022-04-01', investment: -151.6 } + ]); + + expect(investmentsByYear).toEqual([ + { date: '2022-01-01', investment: 0 } + ]); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index 1d6cee0fa..226eaa3d8 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -1,6 +1,6 @@ import { activityDummyData, - symbolProfileDummyData, + assetProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; @@ -85,17 +85,17 @@ describe('PortfolioCalculator', () => { const activities: Activity[] = [ { ...activityDummyData, - date: new Date('2022-01-01'), - feeInAssetProfileCurrency: 0, - feeInBaseCurrency: 0, - quantity: 1, - SymbolProfile: { - ...symbolProfileDummyData, + assetProfile: { + ...assetProfileDummyData, currency: 'USD', dataSource: 'MANUAL', name: 'Penthouse Apartment', symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde' }, + date: new Date('2022-01-01'), + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + quantity: 1, type: 'BUY', unitPriceInAssetProfileCurrency: 500000 } 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 8d90c5bc6..becab8c04 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts @@ -2,7 +2,7 @@ import { Activity } from '@ghostfolio/common/interfaces'; export interface PortfolioOrder extends Pick { assetProfile: Pick< - Activity['SymbolProfile'], + Activity['assetProfile'], 'assetSubClass' | 'currency' | 'dataSource' | 'name' | 'symbol' | 'userId' >; date: string; diff --git a/apps/api/src/app/portfolio/portfolio.service.spec.ts b/apps/api/src/app/portfolio/portfolio.service.spec.ts index 04142c92b..adddb4013 100644 --- a/apps/api/src/app/portfolio/portfolio.service.spec.ts +++ b/apps/api/src/app/portfolio/portfolio.service.spec.ts @@ -373,15 +373,15 @@ describe('PortfolioService', () => { { account, accountId: account.id, + assetProfile: { symbol: 'AAPL' }, quantity: 1, - SymbolProfile: { symbol: 'AAPL' }, type: 'BUY' }, { account: null, accountId: null, + assetProfile: { symbol: 'BABA' }, quantity: 2, - SymbolProfile: { symbol: 'BABA' }, type: 'BUY' } ], @@ -409,8 +409,8 @@ describe('PortfolioService', () => { { account, accountId: account.id, + assetProfile: { symbol: 'AAPL' }, quantity: 1, - SymbolProfile: { symbol: 'AAPL' }, type: 'BUY' } ], diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index e75e54890..6617b8f9b 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -829,10 +829,9 @@ export class PortfolioService { timeWeightedInvestmentWithCurrencyEffect } = holding; - const activitiesOfHolding = activities.filter(({ SymbolProfile }) => { + const activitiesOfHolding = activities.filter(({ assetProfile }) => { return ( - SymbolProfile.dataSource === dataSource && - SymbolProfile.symbol === symbol + assetProfile.dataSource === dataSource && assetProfile.symbol === symbol ); }); @@ -2056,11 +2055,11 @@ export class PortfolioService { .filter(({ isDraft, type }) => { return isDraft === false && type === activityType; }) - .map(({ currency, quantity, SymbolProfile, unitPrice }) => { + .map(({ assetProfile, currency, quantity, unitPrice }) => { return new Big( this.exchangeRateDataService.toCurrency( new Big(quantity).mul(unitPrice).toNumber(), - currency ?? SymbolProfile.currency, + currency ?? assetProfile.currency, userCurrency ) ); @@ -2198,16 +2197,11 @@ export class PortfolioService { } } - for (const { - account, - quantity, - SymbolProfile, - type - } of ordersByAccount) { + for (const { account, assetProfile, quantity, type } of ordersByAccount) { const currentValueOfSymbolInBaseCurrency = getFactor(type) * quantity * - (portfolioItemsNow[SymbolProfile.symbol]?.marketPriceInBaseCurrency ?? + (portfolioItemsNow[assetProfile.symbol]?.marketPriceInBaseCurrency ?? 0); if (accounts[account?.id || UNKNOWN_KEY]?.valueInBaseCurrency) { diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index 56d260d4d..4e75ee9b7 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -78,8 +78,12 @@ export class TransformDataSourceInResponseInterceptor< valueMap, object: data, paths: [ + 'activities[*].assetProfile.dataSource', 'activities[*].dataSource', + + /* @deprecated */ 'activities[*].SymbolProfile.dataSource', + 'assetProfile.dataSource', 'benchmarks[*].dataSource', 'errors[*].dataSource', @@ -88,7 +92,10 @@ export class TransformDataSourceInResponseInterceptor< 'holdings[*].assetProfile.dataSource', 'holdings[*].dataSource', 'items[*].dataSource', + + /* @deprecated */ 'SymbolProfile.dataSource', + 'watchlist[*].dataSource' ] }); diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 397fdce57..3d1c0ceeb 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -409,10 +409,10 @@ export class GfActivitiesPageComponent implements OnInit { activity: { ...aActivity, accountId: aActivity?.accountId, + assetProfile: null, date: new Date(), id: null, fee: 0, - SymbolProfile: null, type: aActivity?.type ?? 'BUY', unitPrice: null }, 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 022a8f2a2..be9ac7b04 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 @@ -119,7 +119,7 @@ export class GfCreateOrUpdateActivityDialogComponent { } public ngOnInit() { - this.currencyOfAssetProfile = this.data.activity?.SymbolProfile?.currency; + this.currencyOfAssetProfile = this.data.activity?.assetProfile?.currency; this.hasPermissionToCreateOwnTag = this.data.user?.settings?.isExperimentalFeatures && hasPermission(this.data.user?.permissions, permissions.createOwnTag); @@ -185,31 +185,31 @@ export class GfCreateOrUpdateActivityDialogComponent { ? this.data.accounts[0].id : this.data.activity?.accountId ], - assetClass: [this.data.activity?.SymbolProfile?.assetClass], - assetSubClass: [this.data.activity?.SymbolProfile?.assetSubClass], + assetClass: [this.data.activity?.assetProfile?.assetClass], + assetSubClass: [this.data.activity?.assetProfile?.assetSubClass], comment: [this.data.activity?.comment], currency: [ - this.data.activity?.SymbolProfile?.currency, + this.data.activity?.assetProfile?.currency, Validators.required ], currencyOfUnitPrice: [ this.data.activity?.currency ?? - this.data.activity?.SymbolProfile?.currency, + this.data.activity?.assetProfile?.currency, Validators.required ], dataSource: [ - this.data.activity?.SymbolProfile?.dataSource, + this.data.activity?.assetProfile?.dataSource, Validators.required ], date: [this.data.activity?.date, Validators.required], fee: [this.data.activity?.fee, Validators.required], - name: [this.data.activity?.SymbolProfile?.name, Validators.required], + name: [this.data.activity?.assetProfile?.name, Validators.required], quantity: [this.data.activity?.quantity, Validators.required], searchSymbol: [ - this.data.activity?.SymbolProfile + this.data.activity?.assetProfile ? { - dataSource: this.data.activity?.SymbolProfile?.dataSource, - symbol: this.data.activity?.SymbolProfile?.symbol + dataSource: this.data.activity?.assetProfile?.dataSource, + symbol: this.data.activity?.assetProfile?.symbol } : null, Validators.required @@ -310,7 +310,7 @@ export class GfCreateOrUpdateActivityDialogComponent { this.activityForm.get('searchSymbol')?.valueChanges.subscribe(() => { if (this.activityForm.get('searchSymbol')?.invalid) { - this.data.activity.SymbolProfile = null; + this.data.activity.assetProfile = null; } else if ( ['BUY', 'DIVIDEND', 'SELL'].includes( this.activityForm.get('type')?.value @@ -454,11 +454,11 @@ export class GfCreateOrUpdateActivityDialogComponent { this.activityForm.get('type')?.disable(); } - if (this.data.activity?.SymbolProfile?.symbol) { + if (this.data.activity?.assetProfile?.symbol) { this.dataService .fetchSymbolItem({ - dataSource: this.data.activity?.SymbolProfile?.dataSource, - symbol: this.data.activity?.SymbolProfile?.symbol + dataSource: this.data.activity?.assetProfile?.dataSource, + symbol: this.data.activity?.assetProfile?.symbol }) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketPrice }) => { @@ -569,7 +569,7 @@ export class GfCreateOrUpdateActivityDialogComponent { }) .pipe( catchError(() => { - this.data.activity.SymbolProfile = null; + this.data.activity.assetProfile = null; this.isLoading = false; diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts index 37faff91f..a3d58f8eb 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts @@ -4,9 +4,9 @@ import { Account } from '@prisma/client'; export interface CreateOrUpdateActivityDialogParams { accounts: Account[]; - activity: Partial> & { + activity: Partial> & { + assetProfile: Activity['assetProfile'] | null; id: string | null; - SymbolProfile: Activity['SymbolProfile'] | null; unitPrice: number | null; }; user: User; diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index f5cc74106..ff5679718 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -165,12 +165,12 @@ export class ImportActivitiesService { private convertToCreateOrderDto({ accountId, + assetProfile, comment, currency, date, fee, quantity, - SymbolProfile, tags, type, unitPrice, @@ -184,10 +184,10 @@ export class ImportActivitiesService { updateAccountBalance, accountId: accountId ?? undefined, comment: comment ?? undefined, - currency: currency ?? SymbolProfile.currency ?? '', - dataSource: SymbolProfile.dataSource, + currency: currency ?? assetProfile.currency ?? '', + dataSource: assetProfile.dataSource, date: date.toString(), - symbol: SymbolProfile.symbol, + symbol: assetProfile.symbol, tags: tags?.map(({ id }) => { return id; }) diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 633e13f24..e071a72c5 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -106,13 +106,20 @@ export const DEFAULT_REDACTED_PATHS = [ 'accounts[*].valueInBaseCurrency', 'activities[*].account.balance', 'activities[*].account.comment', + 'activities[*].assetProfile.symbolMapping', + 'activities[*].assetProfile.watchedByCount', 'activities[*].comment', 'activities[*].fee', 'activities[*].feeInAssetProfileCurrency', 'activities[*].feeInBaseCurrency', 'activities[*].quantity', + + /* @deprecated */ 'activities[*].SymbolProfile.symbolMapping', + + /* @deprecated */ 'activities[*].SymbolProfile.watchedByCount', + 'activities[*].value', 'activities[*].valueInBaseCurrency', 'balance', @@ -143,8 +150,13 @@ export const DEFAULT_REDACTED_PATHS = [ 'platforms[*].balance', 'platforms[*].valueInBaseCurrency', 'quantity', + + /* @deprecated */ 'SymbolProfile.symbolMapping', + + /* @deprecated */ 'SymbolProfile.watchedByCount', + 'totalBalanceInBaseCurrency', 'totalDividendInBaseCurrency', 'totalInterestInBaseCurrency', diff --git a/libs/common/src/lib/interfaces/activities.interface.ts b/libs/common/src/lib/interfaces/activities.interface.ts index b9e64984b..1cc867d6e 100644 --- a/libs/common/src/lib/interfaces/activities.interface.ts +++ b/libs/common/src/lib/interfaces/activities.interface.ts @@ -5,10 +5,16 @@ import { Order, Tag } from '@prisma/client'; export interface Activity extends Order { account?: AccountWithPlatform; + assetProfile: EnhancedSymbolProfile; error?: ActivityError; feeInAssetProfileCurrency: number; feeInBaseCurrency: number; - SymbolProfile: EnhancedSymbolProfile; + + /** + * @deprecated Use `assetProfile` instead + */ + SymbolProfile?: EnhancedSymbolProfile; + tagIds?: string[]; tags?: Tag[]; unitPriceInAssetProfileCurrency: number; diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts index 18c7dc57a..04c8e0c07 100644 --- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts @@ -26,7 +26,13 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { Order, 'currency' | 'date' | 'fee' | 'quantity' | 'type' | 'unitPrice' > & { + assetProfile?: EnhancedSymbolProfile; + + /** + * @deprecated Use `assetProfile` instead + */ SymbolProfile?: EnhancedSymbolProfile; + value: number; valueInBaseCurrency: number; })[]; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index a98d51f56..172059d1c 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -149,9 +149,9 @@ @@ -162,18 +162,18 @@
- {{ element.SymbolProfile?.name }} + {{ element.assetProfile?.name }} @if (element.isDraft) { Draft }
@if ( - element.SymbolProfile?.dataSource !== 'MANUAL' && - !isUUID(element.SymbolProfile?.symbol) + element.assetProfile?.dataSource !== 'MANUAL' && + !isUUID(element.assetProfile?.symbol) ) {
{{ - element.SymbolProfile?.symbol | gfSymbol + element.assetProfile?.symbol | gfSymbol }}
} @@ -309,7 +309,7 @@ class="d-none d-lg-table-cell px-1" mat-cell > - {{ element.currency ?? element.SymbolProfile?.currency }} + {{ element.currency ?? element.assetProfile?.currency }} diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts index 25463e576..957de2262 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts @@ -56,7 +56,7 @@ const activities: Activity[] = [ url: 'https://interactivebrokers.com' } }, - SymbolProfile: { + assetProfile: { assetClass: 'EQUITY', assetSubClass: 'ETF', comment: undefined, @@ -123,7 +123,7 @@ const activities: Activity[] = [ url: 'https://interactivebrokers.com' } }, - SymbolProfile: { + assetProfile: { assetClass: 'EQUITY', assetSubClass: 'ETF', comment: undefined, @@ -190,7 +190,7 @@ const activities: Activity[] = [ url: 'https://interactivebrokers.com' } }, - SymbolProfile: { + assetProfile: { assetClass: 'LIQUIDITY', assetSubClass: 'CRYPTOCURRENCY', comment: undefined, @@ -257,7 +257,7 @@ const activities: Activity[] = [ url: 'https://interactivebrokers.com' } }, - SymbolProfile: { + assetProfile: { assetClass: 'FIXED_INCOME', assetSubClass: 'BOND', comment: 'No data', @@ -324,7 +324,7 @@ const activities: Activity[] = [ url: 'https://interactivebrokers.com' } }, - SymbolProfile: { + assetProfile: { assetClass: 'EQUITY', assetSubClass: 'ETF', comment: undefined, diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index dd792ca35..b1bfcf686 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -285,8 +285,8 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit { } } else if (this.canClickActivity(activity)) { this.activityClicked.emit({ - dataSource: activity.SymbolProfile.dataSource, - symbol: activity.SymbolProfile.symbol + dataSource: activity.assetProfile.dataSource, + symbol: activity.assetProfile.symbol }); } } From df6fa86b1d7ef19d818f9d258f442ea3fd8c0c83 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:22:06 +0200 Subject: [PATCH 62/71] Task/harden validation of holdings in asset profile endpoints (#7320) * Harden validation of holdings * Update changelog --- CHANGELOG.md | 1 + libs/common/src/lib/dtos/create-asset-profile.dto.ts | 3 +++ libs/common/src/lib/dtos/holding.dto.ts | 11 +++++++++++ libs/common/src/lib/dtos/index.ts | 2 ++ libs/common/src/lib/dtos/update-asset-profile.dto.ts | 3 +++ 5 files changed, 20 insertions(+) create mode 100644 libs/common/src/lib/dtos/holding.dto.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9011a3350..1106df29b 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 - Hardened the validation of the countries in the asset profile endpoints +- Hardened the validation of the holdings in the asset profile endpoints - Hardened the validation of the sectors in the asset profile endpoints - Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page diff --git a/libs/common/src/lib/dtos/create-asset-profile.dto.ts b/libs/common/src/lib/dtos/create-asset-profile.dto.ts index 0b0ccb9d3..17a9d12b9 100644 --- a/libs/common/src/lib/dtos/create-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/create-asset-profile.dto.ts @@ -13,6 +13,7 @@ import { } from 'class-validator'; import { CountryDto } from './country.dto'; +import { HoldingDto } from './holding.dto'; import { SectorDto } from './sector.dto'; export class CreateAssetProfileDto { @@ -58,6 +59,8 @@ export class CreateAssetProfileDto { @IsArray() @IsOptional() + @Type(() => HoldingDto) + @ValidateNested({ each: true }) holdings?: Prisma.InputJsonArray; @IsBoolean() diff --git a/libs/common/src/lib/dtos/holding.dto.ts b/libs/common/src/lib/dtos/holding.dto.ts new file mode 100644 index 000000000..8c093e3bc --- /dev/null +++ b/libs/common/src/lib/dtos/holding.dto.ts @@ -0,0 +1,11 @@ +import { IsNumber, IsString, Max, Min } from 'class-validator'; + +export class HoldingDto { + @IsNumber() + @Max(1) + @Min(0) + allocationInPercentage: number; + + @IsString() + name: string; +} diff --git a/libs/common/src/lib/dtos/index.ts b/libs/common/src/lib/dtos/index.ts index c74c68dfd..c531313a6 100644 --- a/libs/common/src/lib/dtos/index.ts +++ b/libs/common/src/lib/dtos/index.ts @@ -11,6 +11,7 @@ import { CreatePlatformDto } from './create-platform.dto'; import { CreateTagDto } from './create-tag.dto'; import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; import { DeleteOwnUserDto } from './delete-own-user.dto'; +import { HoldingDto } from './holding.dto'; import { SectorDto } from './sector.dto'; import { TransferBalanceDto } from './transfer-balance.dto'; import { UpdateAccessDto } from './update-access.dto'; @@ -40,6 +41,7 @@ export { CreateTagDto, CreateWatchlistItemDto, DeleteOwnUserDto, + HoldingDto, SectorDto, TransferBalanceDto, UpdateAccessDto, diff --git a/libs/common/src/lib/dtos/update-asset-profile.dto.ts b/libs/common/src/lib/dtos/update-asset-profile.dto.ts index 5b193ff7e..2204fd0ab 100644 --- a/libs/common/src/lib/dtos/update-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/update-asset-profile.dto.ts @@ -20,6 +20,7 @@ import { } from 'class-validator'; import { CountryDto } from './country.dto'; +import { HoldingDto } from './holding.dto'; import { SectorDto } from './sector.dto'; export class UpdateAssetProfileDto { @@ -55,6 +56,8 @@ export class UpdateAssetProfileDto { @IsArray() @IsOptional() + @Type(() => HoldingDto) + @ValidateNested({ each: true }) holdings?: Prisma.InputJsonArray; @IsBoolean() From a2c28bd4a04854000c43b5b07eced754115bd5e7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:24:07 +0200 Subject: [PATCH 63/71] Task/upgrade Nx to version 23.0.2 (#7321) * Upgrade Nx dependencies to version 23.0.2 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 528 ++++++++++++++++++++++++++-------------------- package.json | 22 +- 3 files changed, 310 insertions(+), 241 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1106df29b..85df11fdb 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 - Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page - Deprecated `SymbolProfile` in favor of `assetProfile` in the activity interface +- Upgraded `Nx` from version `23.0.1` to `23.0.2` ## 3.25.0 - 2026-07-12 diff --git a/package-lock.json b/package-lock.json index 4c0b837ee..ca2585f35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -117,16 +117,16 @@ "@eslint/js": "9.35.0", "@nestjs/schematics": "11.1.0", "@nestjs/testing": "11.1.27", - "@nx/angular": "23.0.1", - "@nx/eslint-plugin": "23.0.1", - "@nx/jest": "23.0.1", - "@nx/js": "23.0.1", - "@nx/module-federation": "23.0.1", - "@nx/nest": "23.0.1", - "@nx/node": "23.0.1", - "@nx/storybook": "23.0.1", - "@nx/web": "23.0.1", - "@nx/workspace": "23.0.1", + "@nx/angular": "23.0.2", + "@nx/eslint-plugin": "23.0.2", + "@nx/jest": "23.0.2", + "@nx/js": "23.0.2", + "@nx/module-federation": "23.0.2", + "@nx/nest": "23.0.2", + "@nx/node": "23.0.2", + "@nx/storybook": "23.0.2", + "@nx/web": "23.0.2", + "@nx/workspace": "23.0.2", "@prisma/config": "7.8.0", "@schematics/angular": "21.2.6", "@storybook/addon-docs": "10.1.10", @@ -154,7 +154,7 @@ "jest": "30.3.0", "jest-environment-jsdom": "30.2.0", "jest-preset-angular": "16.0.0", - "nx": "23.0.1", + "nx": "23.0.2", "prettier": "3.8.4", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "7.8.0", @@ -7835,19 +7835,19 @@ } }, "node_modules/@nx/angular": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-23.0.1.tgz", - "integrity": "sha512-/SJEhwGrAOO35fQjRMwLi3RBqzSaPNViCGqiQldeTokzJqtr9h4QVQxVH+mD+zLdwRI4teoC9taekVrsjXcMoA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-23.0.2.tgz", + "integrity": "sha512-2xeTEyMgeUMLN9YFSnlQj38JxGjkOUmkSKvRCcrTJNZFuZpxMxwW/oXkPJ3EBBjryAMWCA/qyAh3Te0dokw0/g==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/eslint": "23.0.1", - "@nx/js": "23.0.1", - "@nx/module-federation": "23.0.1", - "@nx/rspack": "23.0.1", - "@nx/web": "23.0.1", - "@nx/webpack": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/eslint": "23.0.2", + "@nx/js": "23.0.2", + "@nx/module-federation": "23.0.2", + "@nx/rspack": "23.0.2", + "@nx/web": "23.0.2", + "@nx/webpack": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "@typescript-eslint/type-utils": "^8.0.0", "enquirer": "~2.3.6", @@ -7895,15 +7895,15 @@ } }, "node_modules/@nx/cypress": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-23.0.1.tgz", - "integrity": "sha512-is3oXDoTd2K4C7b2qq3tzKVctBDOdKsfEHIztCwV7ZO3jd9cWl83fLQUL6WI3WVoafo8ALxARRSe1ri4GkzyFg==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-23.0.2.tgz", + "integrity": "sha512-+6RbjMA4KHcBNFIbqOF6oKDWwGsqsJaf2uhH69vDM9rfjJnL7j5CKFzLGSO746Nk4BcLqI0ARR6R8T+w0Ees5g==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/eslint": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/eslint": "23.0.2", + "@nx/js": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "detect-port": "^2.1.0", "semver": "^7.6.3", @@ -7920,9 +7920,9 @@ } }, "node_modules/@nx/devkit": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-23.0.1.tgz", - "integrity": "sha512-A/chuNS1RZwdbRe/Nf+w0qtPEFHLcZNPzo8Abw5mBxyXmy9yvHZpuZuqDbt/lASFU+TEb74xExL1AnKWwqpOIg==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-23.0.2.tgz", + "integrity": "sha512-svkaufC+nucp3stMucqVN0sV4nz9fEgAd33WB9u2fnFWYx/4F0VJcV4ZLyiG3hHrgs2/Gz5K0OnygPqjCsSqMw==", "dev": true, "license": "MIT", "dependencies": { @@ -7932,6 +7932,7 @@ "minimatch": "10.2.5", "semver": "^7.6.3", "tslib": "^2.3.0", + "yaml": "^2.8.3", "yargs-parser": "21.1.1" }, "peerDependencies": { @@ -7949,9 +7950,9 @@ } }, "node_modules/@nx/devkit/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -7991,32 +7992,32 @@ } }, "node_modules/@nx/docker": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/docker/-/docker-23.0.1.tgz", - "integrity": "sha512-H2/wGZa10X2KhM+BzUkZLCI9wDG1kGqDGco1tDWZczKwk9ViOcwV23ljjt5RtIQIson6B7L56pO2F/8WKJ+1CQ==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/docker/-/docker-23.0.2.tgz", + "integrity": "sha512-3eccPjL3kwAndVq7t0V1XQDEkEGz7AhnSzpPwr9IMHgVKqgCIJBbNF1Xi5c3AbPlnjy0GHfnGOeb1/jXf2RuNA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", + "@nx/devkit": "23.0.2", "enquirer": "~2.3.6", "tslib": "^2.3.0" } }, "node_modules/@nx/eslint": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-23.0.1.tgz", - "integrity": "sha512-/P+iXDUsXHeqU7NMDviE/tjJkaVWssc7TLcCoJ6TRy/p+U7HaigLx57VAogBIznldHiyL7KbM7Y0fzqo/hPofw==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-23.0.2.tgz", + "integrity": "sha512-nghKUrUWPj6mZajGkPIC1LxSO+GYWifZjtFnEQU7n5SDffS+QA3mUX5Tr16CZL5tcSLTwpk2/fx3GM/Ji/3DOA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", "semver": "^7.6.3", "tslib": "^2.3.0", "typescript": "~5.9.2" }, "peerDependencies": { - "@nx/jest": "23.0.1", + "@nx/jest": "23.0.2", "@zkochan/js-yaml": "0.0.7", "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" }, @@ -8030,14 +8031,14 @@ } }, "node_modules/@nx/eslint-plugin": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-23.0.1.tgz", - "integrity": "sha512-fZ4fU4fYxvmHYUtiPuh9vg6KItjqaz4c8SHeqOCRvDsbm7NwQidWsGiruk853No5KnmCHfj/En87v/qGOguoGw==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-23.0.2.tgz", + "integrity": "sha512-ckkifbXqmYrA7OgpfxqeqG5feYndN28hfvbMA0yD0nWQFbx24lC6CkzsJJjuIN3RsQa8yrhKIoObYtiMRxdNLQ==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "@typescript-eslint/type-utils": "^8.0.0", "@typescript-eslint/utils": "^8.0.0", @@ -8075,16 +8076,16 @@ } }, "node_modules/@nx/jest": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-23.0.1.tgz", - "integrity": "sha512-F5lhjttIExH8hEJ09cqv3Ac1o8GyGTHpD1OLLWnNAYaN/CPpbI16Ix8VLY/fuwJYf6wcm1/vBL3knbv6LABODA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-23.0.2.tgz", + "integrity": "sha512-NGMUJMzEccj0y/yk+ct+OIFNB1RrdC9C6rFBZ3VFJgxbJlSbxCqQXcO+/668CtGlLVPAZe0baASrCVcvKNUGCQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/reporters": "^30.0.2", "@jest/test-result": "^30.0.2", - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "identity-obj-proxy": "3.0.0", "jest-config": "^30.0.2", @@ -8150,9 +8151,9 @@ } }, "node_modules/@nx/js": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/js/-/js-23.0.1.tgz", - "integrity": "sha512-H8jw1gk7hA8PCXBFC9ocTBpzuXOTvVQ1gA+OlEBMyKqmUaOLNm7yuoOYozwvLsLlCVY27onohSIS8xIdAR/Zow==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-23.0.2.tgz", + "integrity": "sha512-ixtH09vcr6qFaYloCC8JOMLazxTocws2FnHWNbLOpYaO0Nyh4szh8Ttclky7eiI21XYF9z07+0O7/GFlh8hCBA==", "dev": true, "license": "MIT", "dependencies": { @@ -8163,8 +8164,8 @@ "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.6", - "@nx/devkit": "23.0.1", - "@nx/workspace": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/workspace": "23.0.2", "@zkochan/js-yaml": "0.0.7", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^3.1.0", @@ -8228,15 +8229,15 @@ } }, "node_modules/@nx/module-federation": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-23.0.1.tgz", - "integrity": "sha512-scYruTqvCegeTwAHiBL9MNRKb9RaU+XJ3+wxcmZekyNnvcQjmEPPHndE013wHUuwohyEpVFYrVCulbVb+h2Tvg==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-23.0.2.tgz", + "integrity": "sha512-CkSmu0FCb85YU2mNmi96a5csZzXoas9NJZSj5WEvwOWJpfasImpqMrD06ozG27vv6b/TbIPC1MXUfQw5eyxP5g==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", - "@nx/web": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", + "@nx/web": "23.0.2", "@rspack/core": "1.6.8", "express": "^4.21.2", "http-proxy-middleware": "^3.0.5", @@ -8314,9 +8315,9 @@ } }, "node_modules/@nx/module-federation/node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -8596,17 +8597,17 @@ } }, "node_modules/@nx/nest": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-23.0.1.tgz", - "integrity": "sha512-/iSE/XzbOl1C1zDuQgeoKBmQAIJYwAj1PJNrnJKf2fS8Mf1s2wGdWnegNsp7iNmAWvZw3zXxhozFiNzJv/HUzA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-23.0.2.tgz", + "integrity": "sha512-HwawKHpIImnwNfLyULeE+AywB/BIVm9m+ZuoulYP8Gwjvg7Lrq28stnf98sTKEuyGjDDWvTWH4RNHYs0nRXEiA==", "dev": true, "license": "MIT", "dependencies": { "@nestjs/schematics": "^11.0.0", - "@nx/devkit": "23.0.1", - "@nx/eslint": "23.0.1", - "@nx/js": "23.0.1", - "@nx/node": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/eslint": "23.0.2", + "@nx/js": "23.0.2", + "@nx/node": "23.0.2", "semver": "^7.6.3", "tslib": "^2.3.0" }, @@ -8632,17 +8633,17 @@ } }, "node_modules/@nx/node": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/node/-/node-23.0.1.tgz", - "integrity": "sha512-s13ja7MITncR4M/uTGM786Jqco8lJs/9Kvo7jrKFg0Ct8d5XHbu+JFpulmCGPatsBhFA0E/JHSSfR+D9E7MWxA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/node/-/node-23.0.2.tgz", + "integrity": "sha512-WugI9xFXFs0GEKaelYDl+kpbPgwtvleSI1gnCxsA8MukjqZlm5cSTB4/+fOQSFaWhTesR0BZW5SasRQBJTB65A==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/docker": "23.0.1", - "@nx/eslint": "23.0.1", - "@nx/jest": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/docker": "23.0.2", + "@nx/eslint": "23.0.2", + "@nx/jest": "23.0.2", + "@nx/js": "23.0.2", "kill-port": "^1.6.1", "semver": "^7.6.3", "tcp-port-used": "^1.0.2", @@ -8666,9 +8667,9 @@ } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-23.0.1.tgz", - "integrity": "sha512-gQJvgPnbI91DBe23Th2CqD9R/S54cPS3C1f0DhyQ8YEf9rR7EEc+sVGjhgVxlhfOk2W7I1Gy6EkXwpN4aDoW4w==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-23.0.2.tgz", + "integrity": "sha512-9sqhZMVFpF+qM7hq6y2xA4gVK+6RdxRioAwHxorhOZRSXdW7Y7NESs5fm8vOmdddlG07QB7sMefOKLrqCV3zGg==", "cpu": [ "arm64" ], @@ -8680,9 +8681,9 @@ ] }, "node_modules/@nx/nx-darwin-x64": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-23.0.1.tgz", - "integrity": "sha512-e/lvzHKN6gpuD7MqEtfH1fOfnR75E55ytYNt8jaRxKI6EvpCq+Q3MunDuh9GQYAkqDrUqE7AhHrHc+eKATVEHw==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-23.0.2.tgz", + "integrity": "sha512-p6L3AvRhRRaR8Bl3jr76/9H04RdWUQbSgB7agK7GB7vqaLI8RifP2lqeaXcAngzjDAjw2EAf0TjOBP+T67hhcg==", "cpu": [ "x64" ], @@ -8694,9 +8695,9 @@ ] }, "node_modules/@nx/nx-freebsd-x64": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-23.0.1.tgz", - "integrity": "sha512-f582OhSYN9qHpA9Ox9qnr3kZSZ7gQHs7crmBUutmbXmZQB2TDS/TlhvYSNnxudpwHR/tuWGi2IOQqa7zGOZj1Q==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-23.0.2.tgz", + "integrity": "sha512-/py4I8Rp2UURses9H/+SQmgPVnHVSJgPimJLhXIfsRavKGu4RS7Ddu1OyNqSkCT3Otic6ImMTtkufURW22KiEQ==", "cpu": [ "x64" ], @@ -8708,9 +8709,9 @@ ] }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-23.0.1.tgz", - "integrity": "sha512-VjhqPc6E7aiI0e+lowrkVbdyulsmP9fgMdcX1mCzXCEu/XZDcUbZ5qveR964cMhvm5qKn0ILJtJOUqZgmOT3Xg==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-23.0.2.tgz", + "integrity": "sha512-xv2IzeiWJFWi4WjK0ocMkP+ze1lDeoPVCg0xOTqVs40gM66V1wVw3EK077gTqU4m0Bq1wUxe6/I8WaIGlkLgug==", "cpu": [ "arm" ], @@ -8722,9 +8723,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-23.0.1.tgz", - "integrity": "sha512-zX2JdHQejZWB3DRgNsh77qOVYaSSjSLuBP2qIqc7EWVlCUnR7Aj3e65PTIps4LxMMmUp4twZA2ezS0rtyK2A4w==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-23.0.2.tgz", + "integrity": "sha512-ckA6hTXST+agxt/HzPGqMss9qFCZhO9b07o8usygb7QFBYQRXFgcYzhTblq4yiTL5ibJmXAGGh98011fLA2MVw==", "cpu": [ "arm64" ], @@ -8736,9 +8737,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-23.0.1.tgz", - "integrity": "sha512-9lyhxRNBgNYwHt6paq0OLzoKNoEGF5LnNW2YYrgFY8Cjtsg/Q4pcfZ1vB5o9FX9OmUgUQs3t2d4tU8YDukRUWg==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-23.0.2.tgz", + "integrity": "sha512-PAxBxy7m//cKxUeIb6Sk2X5MJ/wjcJcqCx7/L0p8omTt/y/+q1TGpVy6qmJMPUWzNgAULXtsVRSOK4rmiBcrQQ==", "cpu": [ "arm64" ], @@ -8750,9 +8751,9 @@ ] }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-23.0.1.tgz", - "integrity": "sha512-kVszY2xRyyrCXgdCdM1qG1WUhDjNPZxtdWq86a0TyIRJjfJTP9NHqpyhmvj9c2RdZxKVWHotx6fBJzY6Vn2ZrA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-23.0.2.tgz", + "integrity": "sha512-gR146Mo+BjhrIU2fNOJeVjX8HEAHrtmc7IyrD0qD9yqyq0l9Mdx92JnMW3yVQaYIMpPJIqsOvHTDIUbTLFrmTA==", "cpu": [ "x64" ], @@ -8764,9 +8765,9 @@ ] }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-23.0.1.tgz", - "integrity": "sha512-co71K2n4zcS1SYR8EBRlCvIko7M1YycO2tZL0nrCrga87AF5dzCwx+wEclpyCR/4tNOY3FrACk4gIkVskh3CdA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-23.0.2.tgz", + "integrity": "sha512-L7JkaoAI0p+DpAi2CNCqPq8nHWkApQw2td0Cyt+stOx/OJlmEteyc/MXyTHBoMPoopNj1wWANJJQm/G1anOLhQ==", "cpu": [ "x64" ], @@ -8778,9 +8779,9 @@ ] }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-23.0.1.tgz", - "integrity": "sha512-7oma7iy5fbnn+x5AP7SFGMuleAA2R5RZm26dn+faikyQ4PXjoRAikWJJNiOWAeCA0BaMAeVedI6fJeAsVeDUKg==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-23.0.2.tgz", + "integrity": "sha512-B3ePaYeu31ivP3i72Vou5RBYFVrDKCMZgz7Jsax+RvEJa8dNfI+Ynh/PSoXzHudN0YsrekkKbjlxNvp/D6fWFA==", "cpu": [ "arm64" ], @@ -8792,9 +8793,9 @@ ] }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-23.0.1.tgz", - "integrity": "sha512-TE/wvBa2cpkVXmk/AXUQAneong4JReS2hyNpAUONKG1yXU7TDKe0wvn1xQXxAbyspudT9NuCnVtpVuEkRz8S+Q==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-23.0.2.tgz", + "integrity": "sha512-/NiB9w8nYrw7LUkcmwAJ9wis5O+kh3ahSZXMDHVYgFnD8yN7kLql39MNJD+/DGstjNSDvWh45ftqr7mZMi6wpQ==", "cpu": [ "x64" ], @@ -8806,16 +8807,16 @@ ] }, "node_modules/@nx/rspack": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/rspack/-/rspack-23.0.1.tgz", - "integrity": "sha512-Hbn4vUrotIy4EVaPHKQX/ijuuH9+fx3ndaEdzQT0O08iJmVmABozsJ1GmQDYO4aU4qvu3eDsJr1IOQ3jqgp7vQ==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/rspack/-/rspack-23.0.2.tgz", + "integrity": "sha512-Aa/7028o5pPqFbR6qG+kurkrGx+/CfSJiOgwsDz1ND9pd1SYP/Z2J9YXL/tgxsDWMy24FaAJvs4MvHMZvAyvLw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", - "@nx/module-federation": "23.0.1", - "@nx/web": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", + "@nx/module-federation": "23.0.2", + "@nx/web": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "autoprefixer": "^10.4.9", "browserslist": "^4.26.0", @@ -8888,9 +8889,9 @@ } }, "node_modules/@nx/rspack/node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -9228,22 +9229,22 @@ } }, "node_modules/@nx/storybook": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-23.0.1.tgz", - "integrity": "sha512-QNJHgxG4qwHpvpcaJ4sllpmlxHCEJjse1dTBFRGL9U+F71ye/W6vYrmHirKuV9yO1X6DR6D1rQMj6WVTGSnvzw==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-23.0.2.tgz", + "integrity": "sha512-UG6Vh66L6rfSicjUDyP6dkdkX/TimoXxvmHmUXfO2YZlR/uQXIbkav2KpYloea7NPyjX2jehezCEOrTbINEf6A==", "dev": true, "license": "MIT", "dependencies": { - "@nx/cypress": "23.0.1", - "@nx/devkit": "23.0.1", - "@nx/eslint": "23.0.1", - "@nx/js": "23.0.1", + "@nx/cypress": "23.0.2", + "@nx/devkit": "23.0.2", + "@nx/eslint": "23.0.2", + "@nx/js": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "semver": "^7.6.3", "tslib": "^2.3.0" }, "peerDependencies": { - "@nx/web": "23.0.1", + "@nx/web": "23.0.2", "storybook": ">=8.0.0 <11.0.0" }, "peerDependenciesMeta": { @@ -9253,26 +9254,26 @@ } }, "node_modules/@nx/web": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/web/-/web-23.0.1.tgz", - "integrity": "sha512-0iwqCPJ5A27/XzqF6637dcRmJO3/TY7nQrEobS0yJ4W2xhvCIRwsWmPkuUAZgjd4622yQauHAwPQAnkIEILPiQ==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-23.0.2.tgz", + "integrity": "sha512-PIUMDB79yvt6nj9ypLTCYZfA7N9v8mvWEHRbt43rusrzl3P4wqZIiveuyIstmt4HuvIHW3rRjbZTSs/eYrkqRg==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", "detect-port": "^2.1.0", "http-server": "^14.1.0", "picocolors": "^1.1.0", "tslib": "^2.3.0" }, "peerDependencies": { - "@nx/cypress": "23.0.1", - "@nx/eslint": "23.0.1", - "@nx/jest": "23.0.1", - "@nx/playwright": "23.0.1", - "@nx/vite": "23.0.1", - "@nx/webpack": "23.0.1" + "@nx/cypress": "23.0.2", + "@nx/eslint": "23.0.2", + "@nx/jest": "23.0.2", + "@nx/playwright": "23.0.2", + "@nx/vite": "23.0.2", + "@nx/webpack": "23.0.2" }, "peerDependenciesMeta": { "@nx/cypress": { @@ -9296,15 +9297,15 @@ } }, "node_modules/@nx/webpack": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-23.0.1.tgz", - "integrity": "sha512-0ELnKItRtIlcHAN8AaSY4uwabA62aAFDCvYFqadQCTeN5eBYl1VGfzenWMvGyVZoU7K9dYtPVSkZSUhltvHQgw==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-23.0.2.tgz", + "integrity": "sha512-TbMBG8mLaD7SImTNKH9CZyhgciS6vJZ6/dPK01aHJ4lamKtnFyRGsktgPkLvhKH9jJqLSWSWb4IWAsoHMe46GA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", - "@nx/devkit": "23.0.1", - "@nx/js": "23.0.1", + "@nx/devkit": "23.0.2", + "@nx/js": "23.0.2", "@phenomnomnominal/tsquery": "~6.2.0", "ajv": "^8.0.0", "autoprefixer": "^10.4.9", @@ -9331,7 +9332,7 @@ "source-map-loader": "^5.0.0", "style-loader": "^3.3.0", "terser-webpack-plugin": "^5.3.3", - "ts-loader": "^9.3.1", + "ts-loader": "^9.5.7", "tsconfig-paths-webpack-plugin": "4.2.0", "tslib": "^2.3.0", "webpack-node-externals": "^3.0.0", @@ -9461,17 +9462,17 @@ "license": "MIT" }, "node_modules/@nx/workspace": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-23.0.1.tgz", - "integrity": "sha512-VdbvMTSEzp3ONZwiy83XEu8ktykC8aEI7M4mqKs5RNKHBFg3jtao2NFo3wDqHqnn1q9Fdaj8EbyUn08BUR5L3w==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-23.0.2.tgz", + "integrity": "sha512-7As52HT7l7ypwukR0cKZIDZ771O0hYTDwkfJmzWPHl3YghiROnK3/Xlh/84ITo+4gtCMORUyZXZgQfOi8136ug==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "23.0.1", + "@nx/devkit": "23.0.2", "@zkochan/js-yaml": "0.0.7", "chalk": "^4.1.0", "enquirer": "~2.3.6", - "nx": "23.0.1", + "nx": "23.0.2", "picomatch": "4.0.4", "semver": "^7.6.3", "tslib": "^2.3.0", @@ -15072,17 +15073,45 @@ } }, "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, + "node_modules/axios/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -17277,9 +17306,9 @@ } }, "node_modules/css-minimizer-webpack-plugin/node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "dev": true, "funding": [ { @@ -26840,9 +26869,9 @@ "license": "MIT" }, "node_modules/nx": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/nx/-/nx-23.0.1.tgz", - "integrity": "sha512-HnK0Ke8FcPeVQffYm1oyzkLNn7khrI8SeDeC3iyLhw/UEMCB24hjI5JSs6Amlyeb0/GaeiuQuts8NkQKd/NpGA==", + "version": "23.0.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-23.0.2.tgz", + "integrity": "sha512-e5H6ceqj0Z8ovAmtsiHXswwpiNPrr1DRhvJMTpc2AW8G1za9PKxk3bP5josShsIrmGEsOlBNZZsxszXA2+Q2dw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -26855,12 +26884,13 @@ "@tybys/wasm-util": "0.9.0", "@yarnpkg/lockfile": "1.1.0", "@zkochan/js-yaml": "0.0.7", + "agent-base": "6.0.2", "ansi-colors": "4.1.3", "ansi-regex": "5.0.1", "ansi-styles": "4.3.0", "argparse": "2.0.1", "asynckit": "0.4.0", - "axios": "1.16.0", + "axios": "1.16.1", "balanced-match": "4.0.3", "base64-js": "1.5.1", "bl": "4.1.0", @@ -26875,6 +26905,7 @@ "color-convert": "2.0.1", "color-name": "1.1.4", "combined-stream": "1.0.8", + "debug": "4.4.3", "defaults": "1.0.4", "define-lazy-prop": "2.0.0", "delayed-stream": "1.0.0", @@ -26905,6 +26936,7 @@ "has-symbols": "1.1.0", "has-tostringtag": "1.0.2", "hasown": "2.0.4", + "https-proxy-agent": "5.0.1", "ieee754": "1.2.1", "ignore": "7.0.5", "inherits": "2.0.4", @@ -26924,6 +26956,7 @@ "mimic-fn": "2.1.0", "minimatch": "10.2.5", "minimist": "1.2.8", + "ms": "2.1.3", "npm-run-path": "4.0.1", "once": "1.4.0", "onetime": "5.1.2", @@ -26964,16 +26997,16 @@ "nx-cloud": "dist/bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "23.0.1", - "@nx/nx-darwin-x64": "23.0.1", - "@nx/nx-freebsd-x64": "23.0.1", - "@nx/nx-linux-arm-gnueabihf": "23.0.1", - "@nx/nx-linux-arm64-gnu": "23.0.1", - "@nx/nx-linux-arm64-musl": "23.0.1", - "@nx/nx-linux-x64-gnu": "23.0.1", - "@nx/nx-linux-x64-musl": "23.0.1", - "@nx/nx-win32-arm64-msvc": "23.0.1", - "@nx/nx-win32-x64-msvc": "23.0.1" + "@nx/nx-darwin-arm64": "23.0.2", + "@nx/nx-darwin-x64": "23.0.2", + "@nx/nx-freebsd-x64": "23.0.2", + "@nx/nx-linux-arm-gnueabihf": "23.0.2", + "@nx/nx-linux-arm64-gnu": "23.0.2", + "@nx/nx-linux-arm64-musl": "23.0.2", + "@nx/nx-linux-x64-gnu": "23.0.2", + "@nx/nx-linux-x64-musl": "23.0.2", + "@nx/nx-win32-arm64-msvc": "23.0.2", + "@nx/nx-win32-x64-msvc": "23.0.2" }, "peerDependencies": { "@swc-node/register": "^1.11.1", @@ -27039,6 +27072,19 @@ "tslib": "^2.4.0" } }, + "node_modules/nx/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/nx/node_modules/balanced-match": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", @@ -27128,6 +27174,20 @@ "node": ">=0.8.0" } }, + "node_modules/nx/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/nx/node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -27256,6 +27316,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/nx/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nx/node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -32460,9 +32527,9 @@ } }, "node_modules/svgo": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", - "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz", + "integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==", "dev": true, "license": "MIT", "dependencies": { @@ -33192,15 +33259,15 @@ } }, "node_modules/ts-checker-rspack-plugin": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ts-checker-rspack-plugin/-/ts-checker-rspack-plugin-1.5.1.tgz", - "integrity": "sha512-h0jF3PAIrG+UA2nZ2OPUEt0NywkpiJkydkoVg/Kd1OFPFlrayNBJoSP0zeNPUpdsFEt7ICX1BjENCfOH9nmMyA==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/ts-checker-rspack-plugin/-/ts-checker-rspack-plugin-1.5.2.tgz", + "integrity": "sha512-YtdNbLnJ9fU6itfQky0K9Lebb/N0TUSbSVUByyoGt27em3hiTCVnfW2MzxFUHpdMv5+7xPmoS8YBwdDyHfEaGQ==", "dev": true, "license": "MIT", "dependencies": { "@rspack/lite-tapable": "^1.1.2", "chokidar": "^3.6.0", - "memfs": "^4.57.7", + "memfs": "^4.57.8", "picocolors": "^1.1.1" }, "peerDependencies": { @@ -33252,14 +33319,14 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-core": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.8.tgz", - "integrity": "sha512-YzVbwggV9452VCeHgo0bjsTaUt1O7JE0XpEsPar93nn/+RAwXk0mb1Y+f5EDJ3TRtRCFe+Ck5RuojdfB4jeHVw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.64.0.tgz", + "integrity": "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.57.8", - "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "engines": { @@ -33274,15 +33341,15 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.8.tgz", - "integrity": "sha512-vmClyvCQMxgqz7uamDiGtRfp4MjzOznk3pcQjCxlIwJcw7TWeyr+bF30hI0x8NxdtNOGMg1pHM74VDIXOeyjuw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.64.0.tgz", + "integrity": "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.8", - "@jsonjoy.com/fs-node-builtins": "4.57.8", - "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "engines": { @@ -33297,17 +33364,17 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-node": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.8.tgz", - "integrity": "sha512-IPEOlDYSnTDYpjQlQg2F8h+eqxKQN3sdbroI0WrteRiQZ462HzVpBo9ZZX485njz4nAacoe3fd4iDiIhk+k5Hg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.64.0.tgz", + "integrity": "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.8", - "@jsonjoy.com/fs-node-builtins": "4.57.8", - "@jsonjoy.com/fs-node-utils": "4.57.8", - "@jsonjoy.com/fs-print": "4.57.8", - "@jsonjoy.com/fs-snapshot": "4.57.8", + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/fs-print": "4.64.0", + "@jsonjoy.com/fs-snapshot": "4.64.0", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, @@ -33323,9 +33390,9 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.8.tgz", - "integrity": "sha512-mxXSXw8zZwRVakcjLqR2I/psy4gURFSASZS10kKJ2kJw05GC2nXGroGrWVHxwgkxXgQLsFQnB74QaLzsxzdL/w==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.64.0.tgz", + "integrity": "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -33340,15 +33407,15 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.8.tgz", - "integrity": "sha512-AWZcT/4+H+iDl4XCukbXrarvwEgOrf/prFI5/7eg4ix9FxqVsZysIDJd1Kjd+AjlCeHKHJOaRqjLd5HiGSCJEw==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.64.0.tgz", + "integrity": "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-fsa": "4.57.8", - "@jsonjoy.com/fs-node-builtins": "4.57.8", - "@jsonjoy.com/fs-node-utils": "4.57.8" + "@jsonjoy.com/fs-fsa": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0" }, "engines": { "node": ">=10.0" @@ -33362,13 +33429,14 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.8.tgz", - "integrity": "sha512-E/bJ7sQAb4pu9nbeJhbULU3WnqWrswte4N9Js/oHt7aHB746S8/XBqKlcbrqIgnD3095XluovNEZuu5ONT230g==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.64.0.tgz", + "integrity": "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.57.8" + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "glob-to-regex.js": "^1.0.1" }, "engines": { "node": ">=10.0" @@ -33382,13 +33450,13 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-print": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.8.tgz", - "integrity": "sha512-DfzhOBpmvNu5P/KSe4NNQaOnvNliTdcf0qrh/4EReErF/XUQXYkd0vZl/OiJCm/qjEEo8DWRstliw2/JNS84dA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.64.0.tgz", + "integrity": "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.64.0", "tree-dump": "^1.1.0" }, "engines": { @@ -33403,14 +33471,14 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.8.tgz", - "integrity": "sha512-L+eqKaWOHLDaiMv1dh/EWQ4hA+o6xAhWSumTo3Teg7OM18jU/KE13/e8Mfal+eAZ/pSl4wIhKHcDiwapJzC8Wg==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.64.0.tgz", + "integrity": "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.57.8", + "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, @@ -33560,20 +33628,20 @@ } }, "node_modules/ts-checker-rspack-plugin/node_modules/memfs": { - "version": "4.57.8", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.8.tgz", - "integrity": "sha512-bApYhn8BLpFAnAQmFfEl/NPN+8qx5Ar3V4Qt3ek23mVwBEElzV7c6XoPkb/PCG8ZFpowCEpHcPwMFTwHS7tSMA==", + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.64.0.tgz", + "integrity": "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.57.8", - "@jsonjoy.com/fs-fsa": "4.57.8", - "@jsonjoy.com/fs-node": "4.57.8", - "@jsonjoy.com/fs-node-builtins": "4.57.8", - "@jsonjoy.com/fs-node-to-fsa": "4.57.8", - "@jsonjoy.com/fs-node-utils": "4.57.8", - "@jsonjoy.com/fs-print": "4.57.8", - "@jsonjoy.com/fs-snapshot": "4.57.8", + "@jsonjoy.com/fs-core": "4.64.0", + "@jsonjoy.com/fs-fsa": "4.64.0", + "@jsonjoy.com/fs-node": "4.64.0", + "@jsonjoy.com/fs-node-builtins": "4.64.0", + "@jsonjoy.com/fs-node-to-fsa": "4.64.0", + "@jsonjoy.com/fs-node-utils": "4.64.0", + "@jsonjoy.com/fs-print": "4.64.0", + "@jsonjoy.com/fs-snapshot": "4.64.0", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", diff --git a/package.json b/package.json index bc6b86836..f705874b6 100644 --- a/package.json +++ b/package.json @@ -161,16 +161,16 @@ "@eslint/js": "9.35.0", "@nestjs/schematics": "11.1.0", "@nestjs/testing": "11.1.27", - "@nx/angular": "23.0.1", - "@nx/eslint-plugin": "23.0.1", - "@nx/jest": "23.0.1", - "@nx/js": "23.0.1", - "@nx/module-federation": "23.0.1", - "@nx/nest": "23.0.1", - "@nx/node": "23.0.1", - "@nx/storybook": "23.0.1", - "@nx/web": "23.0.1", - "@nx/workspace": "23.0.1", + "@nx/angular": "23.0.2", + "@nx/eslint-plugin": "23.0.2", + "@nx/jest": "23.0.2", + "@nx/js": "23.0.2", + "@nx/module-federation": "23.0.2", + "@nx/nest": "23.0.2", + "@nx/node": "23.0.2", + "@nx/storybook": "23.0.2", + "@nx/web": "23.0.2", + "@nx/workspace": "23.0.2", "@prisma/config": "7.8.0", "@schematics/angular": "21.2.6", "@storybook/addon-docs": "10.1.10", @@ -198,7 +198,7 @@ "jest": "30.3.0", "jest-environment-jsdom": "30.2.0", "jest-preset-angular": "16.0.0", - "nx": "23.0.1", + "nx": "23.0.2", "prettier": "3.8.4", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "7.8.0", From 00c37f026cc1c609d69579c9bc10ffcc9722c097 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:30:58 +0200 Subject: [PATCH 64/71] Task/harden validation of scraper configuration in asset profile endpoint (#7322) * Harden validation of scraper configuration * Update changelog --- CHANGELOG.md | 1 + libs/common/src/lib/dtos/index.ts | 2 ++ .../src/lib/dtos/scraper-configuration.dto.ts | 35 +++++++++++++++++++ .../src/lib/dtos/update-asset-profile.dto.ts | 3 ++ 4 files changed, 41 insertions(+) create mode 100644 libs/common/src/lib/dtos/scraper-configuration.dto.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 85df11fdb..a0a652c44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Hardened the validation of the countries in the asset profile endpoints - Hardened the validation of the holdings in the asset profile endpoints +- Hardened the validation of the scraper configuration in the asset profile endpoint - Hardened the validation of the sectors in the asset profile endpoints - Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page diff --git a/libs/common/src/lib/dtos/index.ts b/libs/common/src/lib/dtos/index.ts index c531313a6..785115356 100644 --- a/libs/common/src/lib/dtos/index.ts +++ b/libs/common/src/lib/dtos/index.ts @@ -12,6 +12,7 @@ import { CreateTagDto } from './create-tag.dto'; import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; import { DeleteOwnUserDto } from './delete-own-user.dto'; import { HoldingDto } from './holding.dto'; +import { ScraperConfigurationDto } from './scraper-configuration.dto'; import { SectorDto } from './sector.dto'; import { TransferBalanceDto } from './transfer-balance.dto'; import { UpdateAccessDto } from './update-access.dto'; @@ -42,6 +43,7 @@ export { CreateWatchlistItemDto, DeleteOwnUserDto, HoldingDto, + ScraperConfigurationDto, SectorDto, TransferBalanceDto, UpdateAccessDto, diff --git a/libs/common/src/lib/dtos/scraper-configuration.dto.ts b/libs/common/src/lib/dtos/scraper-configuration.dto.ts new file mode 100644 index 000000000..52cadb80c --- /dev/null +++ b/libs/common/src/lib/dtos/scraper-configuration.dto.ts @@ -0,0 +1,35 @@ +import { + IsIn, + IsNumber, + IsObject, + IsOptional, + IsString, + IsUrl +} from 'class-validator'; + +export class ScraperConfigurationDto { + @IsNumber() + @IsOptional() + defaultMarketPrice?: number; + + @IsObject() + @IsOptional() + headers?: { [key: string]: string }; + + @IsOptional() + @IsString() + locale?: string; + + @IsIn(['instant', 'lazy']) + @IsOptional() + mode?: 'instant' | 'lazy'; + + @IsString() + selector: string; + + @IsUrl({ + protocols: ['http', 'https'], + require_protocol: true + }) + url: string; +} diff --git a/libs/common/src/lib/dtos/update-asset-profile.dto.ts b/libs/common/src/lib/dtos/update-asset-profile.dto.ts index 2204fd0ab..a6e2230e8 100644 --- a/libs/common/src/lib/dtos/update-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/update-asset-profile.dto.ts @@ -21,6 +21,7 @@ import { import { CountryDto } from './country.dto'; import { HoldingDto } from './holding.dto'; +import { ScraperConfigurationDto } from './scraper-configuration.dto'; import { SectorDto } from './sector.dto'; export class UpdateAssetProfileDto { @@ -70,6 +71,8 @@ export class UpdateAssetProfileDto { @IsObject() @IsOptional() + @Type(() => ScraperConfigurationDto) + @ValidateNested() scraperConfiguration?: Prisma.InputJsonObject; @IsArray() From 69254032a384655d2f8447674bc53a9508ed068e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:53:48 +0200 Subject: [PATCH 65/71] Feature/add markets endpoint for Fear & Greed index to Ghostfolio data provider (#7323) * Add markets endpoint * Update changelog --- CHANGELOG.md | 4 ++ .../ghostfolio/ghostfolio.controller.ts | 39 ++++++++++++++ .../ghostfolio/ghostfolio.module.ts | 2 + .../ghostfolio/ghostfolio.service.ts | 33 +++++++++++- .../market-data/market-data.controller.ts | 43 ++------------- .../market-data/market-data.module.ts | 8 +-- apps/api/src/app/symbol/symbol.service.ts | 46 ++++++++++++++++ .../src/app/pages/api/api-page.component.ts | 22 +++++++- apps/client/src/app/pages/api/api-page.html | 52 +++++++++++++++++++ 9 files changed, 200 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a652c44..4586c668d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added the markets endpoint for the _Fear & Greed Index_ (market mood) to the `GHOSTFOLIO` data provider + ### Changed - Hardened the validation of the countries in the asset profile endpoints diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts index 04165e9a1..5d6979acc 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts @@ -8,6 +8,7 @@ import { DividendsResponse, HistoricalResponse, LookupResponse, + MarketDataOfMarketsResponse, QuotesResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; @@ -19,6 +20,7 @@ import { HttpException, Inject, Param, + ParseIntPipe, Query, UseGuards, Version @@ -203,6 +205,43 @@ export class GhostfolioController { } } + @Get('markets') + @HasPermission(permissions.enableDataProviderGhostfolio) + @UseGuards(AuthGuard('api-key'), HasPermissionGuard) + public async getMarketDataOfMarkets( + @Query('includeHistoricalData', new ParseIntPipe({ optional: true })) + includeHistoricalData = 0 + ): Promise { + const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests(); + + if ( + this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS), + StatusCodes.TOO_MANY_REQUESTS + ); + } + + try { + const marketDataOfMarkets = + await this.ghostfolioService.getMarketDataOfMarkets({ + includeHistoricalData + }); + + await this.ghostfolioService.incrementDailyRequests({ + userId: this.request.user.id + }); + + return marketDataOfMarkets; + } catch { + throw new HttpException( + getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), + StatusCodes.INTERNAL_SERVER_ERROR + ); + } + } + @Get('quotes') @HasPermission(permissions.enableDataProviderGhostfolio) @UseGuards(AuthGuard('api-key'), HasPermissionGuard) diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts index 484f30ee3..1b8788ecb 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts @@ -1,4 +1,5 @@ import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; +import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module'; import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service'; @@ -33,6 +34,7 @@ import { GhostfolioService } from './ghostfolio.service'; PrismaModule, PropertyModule, RedisCacheModule, + SymbolModule, SymbolProfileModule ], providers: [ diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts index caff86ae0..abbb5cbf6 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -1,3 +1,4 @@ +import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { GhostfolioService as GhostfolioDataProviderService } from '@ghostfolio/api/services/data-provider/ghostfolio/ghostfolio.service'; @@ -25,6 +26,7 @@ import { HistoricalResponse, LookupItem, LookupResponse, + MarketDataOfMarketsResponse, QuotesResponse } from '@ghostfolio/common/interfaces'; import { UserWithSettings } from '@ghostfolio/common/types'; @@ -32,6 +34,7 @@ import { UserWithSettings } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import { Big } from 'big.js'; +import { isEmpty } from 'lodash'; @Injectable() export class GhostfolioService { @@ -42,7 +45,8 @@ export class GhostfolioService { private readonly dataProviderService: DataProviderService, private readonly fetchService: FetchService, private readonly prismaService: PrismaService, - private readonly propertyService: PropertyService + private readonly propertyService: PropertyService, + private readonly symbolService: SymbolService ) {} public async getAssetProfile({ symbol }: GetAssetProfileParams) { @@ -198,6 +202,33 @@ export class GhostfolioService { } } + public async getMarketDataOfMarkets({ + includeHistoricalData + }: { + includeHistoricalData: number; + }): Promise { + try { + const marketDataOfMarkets = + await this.symbolService.getMarketDataOfMarkets({ + includeHistoricalData + }); + + for (const symbolItem of Object.values( + marketDataOfMarkets.fearAndGreedIndex + )) { + if (!isEmpty(symbolItem)) { + symbolItem.dataSource = DataSource.GHOSTFOLIO; + } + } + + return marketDataOfMarkets; + } catch (error) { + this.logger.error(error); + + throw error; + } + } + public async getMaxDailyRequests() { return parseInt( (await this.propertyService.getByKey( diff --git a/apps/api/src/app/endpoints/market-data/market-data.controller.ts b/apps/api/src/app/endpoints/market-data/market-data.controller.ts index 105bb0780..03d50c284 100644 --- a/apps/api/src/app/endpoints/market-data/market-data.controller.ts +++ b/apps/api/src/app/endpoints/market-data/market-data.controller.ts @@ -1,14 +1,8 @@ import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; -import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { - ghostfolioFearAndGreedIndexDataSourceCryptocurrencies, - ghostfolioFearAndGreedIndexSymbolCryptocurrencies, - ghostfolioFearAndGreedIndexSymbolStocks -} from '@ghostfolio/common/config'; import { UpdateBulkMarketDataDto } from '@ghostfolio/common/dtos'; import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; import { MarketDataOfMarketsResponse } from '@ghostfolio/common/interfaces'; @@ -36,7 +30,6 @@ import { getReasonPhrase, StatusCodes } from 'http-status-codes'; @Controller('market-data') export class MarketDataController { public constructor( - private readonly dataProviderService: DataProviderService, private readonly marketDataService: MarketDataService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly symbolProfileService: SymbolProfileService, @@ -50,39 +43,9 @@ export class MarketDataController { @Query('includeHistoricalData', new ParseIntPipe({ optional: true })) includeHistoricalData = 0 ): Promise { - const [ - marketDataFearAndGreedIndexCryptocurrencies, - marketDataFearAndGreedIndexStocks - ] = await Promise.all([ - this.symbolService.get({ - includeHistoricalData, - dataGatheringItem: { - dataSource: ghostfolioFearAndGreedIndexDataSourceCryptocurrencies, - symbol: ghostfolioFearAndGreedIndexSymbolCryptocurrencies - }, - useIntradayData: true - }), - this.symbolService.get({ - includeHistoricalData, - dataGatheringItem: { - dataSource: - this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(), - symbol: ghostfolioFearAndGreedIndexSymbolStocks - }, - useIntradayData: true - }) - ]); - - return { - fearAndGreedIndex: { - CRYPTOCURRENCIES: { - ...marketDataFearAndGreedIndexCryptocurrencies - }, - STOCKS: { - ...marketDataFearAndGreedIndexStocks - } - } - }; + return this.symbolService.getMarketDataOfMarkets({ + includeHistoricalData + }); } @Post(':dataSource/:symbol') diff --git a/apps/api/src/app/endpoints/market-data/market-data.module.ts b/apps/api/src/app/endpoints/market-data/market-data.module.ts index 47ce11502..1de10907b 100644 --- a/apps/api/src/app/endpoints/market-data/market-data.module.ts +++ b/apps/api/src/app/endpoints/market-data/market-data.module.ts @@ -1,5 +1,4 @@ import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module'; -import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { MarketDataModule as MarketDataServiceModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; @@ -9,11 +8,6 @@ import { MarketDataController } from './market-data.controller'; @Module({ controllers: [MarketDataController], - imports: [ - DataProviderModule, - MarketDataServiceModule, - SymbolModule, - SymbolProfileModule - ] + imports: [MarketDataServiceModule, SymbolModule, SymbolProfileModule] }) export class MarketDataModule {} diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 39d279de1..2ace4feeb 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -1,6 +1,11 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; +import { + ghostfolioFearAndGreedIndexDataSourceCryptocurrencies, + ghostfolioFearAndGreedIndexSymbolCryptocurrencies, + ghostfolioFearAndGreedIndexSymbolStocks +} from '@ghostfolio/common/config'; import { DATE_FORMAT, getAssetProfileIdentifier @@ -9,6 +14,7 @@ import { DataProviderHistoricalResponse, HistoricalDataItem, LookupResponse, + MarketDataOfMarketsResponse, SymbolItem } from '@ghostfolio/common/interfaces'; import { UserWithSettings } from '@ghostfolio/common/types'; @@ -122,6 +128,46 @@ export class SymbolService { }; } + public async getMarketDataOfMarkets({ + includeHistoricalData + }: { + includeHistoricalData: number; + }): Promise { + const [ + marketDataFearAndGreedIndexCryptocurrencies, + marketDataFearAndGreedIndexStocks + ] = await Promise.all([ + this.get({ + includeHistoricalData, + dataGatheringItem: { + dataSource: ghostfolioFearAndGreedIndexDataSourceCryptocurrencies, + symbol: ghostfolioFearAndGreedIndexSymbolCryptocurrencies + }, + useIntradayData: true + }), + this.get({ + includeHistoricalData, + dataGatheringItem: { + dataSource: + this.dataProviderService.getDataSourceForFearAndGreedIndexStocks(), + symbol: ghostfolioFearAndGreedIndexSymbolStocks + }, + useIntradayData: true + }) + ]); + + return { + fearAndGreedIndex: { + CRYPTOCURRENCIES: { + ...marketDataFearAndGreedIndexCryptocurrencies + }, + STOCKS: { + ...marketDataFearAndGreedIndexStocks + } + } + }; + } + public async lookup({ includeIndices = false, query, diff --git a/apps/client/src/app/pages/api/api-page.component.ts b/apps/client/src/app/pages/api/api-page.component.ts index 0f39c67d6..3373977cc 100644 --- a/apps/client/src/app/pages/api/api-page.component.ts +++ b/apps/client/src/app/pages/api/api-page.component.ts @@ -1,3 +1,4 @@ +import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component'; import { HEADER_KEY_SKIP_INTERCEPTOR, HEADER_KEY_TOKEN @@ -10,6 +11,7 @@ import { DividendsResponse, HistoricalResponse, LookupResponse, + MarketDataOfMarketsResponse, QuotesResponse } from '@ghostfolio/common/interfaces'; @@ -32,7 +34,12 @@ import { FetchFailure, FetchResult } from './interfaces/interfaces'; @Component({ host: { class: 'page' }, - imports: [CommonModule, MatCardModule, NgxSkeletonLoaderModule], + imports: [ + CommonModule, + GfFearAndGreedIndexComponent, + MatCardModule, + NgxSkeletonLoaderModule + ], selector: 'gf-api-page', styleUrls: ['./api-page.scss'], templateUrl: './api-page.html' @@ -48,6 +55,9 @@ export class GfApiPageComponent implements OnInit { >; protected isinLookupItems$: Observable>; protected lookupItems$: Observable>; + protected marketDataOfMarkets$: Observable< + FetchResult + >; protected quotes$: Observable>; protected status$: Observable< FetchResult @@ -68,6 +78,7 @@ export class GfApiPageComponent implements OnInit { this.historicalData$ = this.fetchHistoricalData({ symbol: 'AAPL' }); this.isinLookupItems$ = this.fetchLookupItems({ query: 'US0378331005' }); this.lookupItems$ = this.fetchLookupItems({ query: 'apple' }); + this.marketDataOfMarkets$ = this.fetchMarketDataOfMarkets(); this.quotes$ = this.fetchQuotes({ symbols: ['AAPL', 'VOO'] }); this.status$ = this.fetchStatus(); } @@ -172,6 +183,15 @@ export class GfApiPageComponent implements OnInit { ); } + private fetchMarketDataOfMarkets() { + return this.http + .get( + '/api/v1/data-providers/ghostfolio/markets', + { headers: this.getHeaders() } + ) + .pipe(this.catchFetchFailure(), takeUntilDestroyed(this.destroyRef)); + } + private fetchQuotes({ symbols }: { symbols: string[] }) { const params = new HttpParams().set('symbols', symbols.join(',')); diff --git a/apps/client/src/app/pages/api/api-page.html b/apps/client/src/app/pages/api/api-page.html index 07f5ec981..b2d9d03ac 100644 --- a/apps/client/src/app/pages/api/api-page.html +++ b/apps/client/src/app/pages/api/api-page.html @@ -168,6 +168,58 @@ } +
+ @let marketDataOfMarkets = marketDataOfMarkets$ | async; +
+ + + Markets + Stocks + + + @if (isFetchFailure(marketDataOfMarkets)) { + 🔴 {{ marketDataOfMarkets.fetchError }} + } @else if (marketDataOfMarkets) { + + } @else { + + } + + +
+
+ + + Markets + Cryptocurrencies + + + @if (isFetchFailure(marketDataOfMarkets)) { + 🔴 {{ marketDataOfMarkets.fetchError }} + } @else if (marketDataOfMarkets) { + + } @else { + + } + + +
+
From 5d40f2fb1e9311a92386139a2651d307b94ce34c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:54:17 +0200 Subject: [PATCH 66/71] Task/upgrade countries-list to version 3.4.0 (#7314) * Update countries-list to version 3.4.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4586c668d..c012d540c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rounded the value of the _Fear & Greed Index_ (market mood) in the twitter bot service - Set the change detection strategy to `OnPush` in the _X-ray_ page - Deprecated `SymbolProfile` in favor of `assetProfile` in the activity interface +- Upgraded `countries-list` from version `3.3.0` to `3.4.0` - Upgraded `Nx` from version `23.0.1` to `23.0.2` ## 3.25.0 - 2026-07-12 diff --git a/package-lock.json b/package-lock.json index ca2585f35..9cc8f9160 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,7 @@ "color": "5.0.3", "cookie-parser": "1.4.7", "countries-and-timezones": "3.9.0", - "countries-list": "3.3.0", + "countries-list": "3.4.0", "countup.js": "2.10.0", "date-fns": "4.4.0", "dotenv": "17.2.3", @@ -17062,9 +17062,9 @@ } }, "node_modules/countries-list": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.3.0.tgz", - "integrity": "sha512-XRUjS+dcZuNh/fg3+mka3bXgcg4TbQZ1gaK5IJqO6qulerBANl1bmrd20P2dgmPkBpP+5FnejiSF1gd7bgAg+g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.4.0.tgz", + "integrity": "sha512-SonKkjlEGUNz/Ugj6akOMUl8/F6EBVbLq/nQbDKhZo60EFyNgsVIgDP521J9pGhywikV514Ct4BueIqiOnvBBA==", "license": "MIT" }, "node_modules/countup.js": { diff --git a/package.json b/package.json index f705874b6..c484e3617 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "color": "5.0.3", "cookie-parser": "1.4.7", "countries-and-timezones": "3.9.0", - "countries-list": "3.3.0", + "countries-list": "3.4.0", "countup.js": "2.10.0", "date-fns": "4.4.0", "dotenv": "17.2.3", From 4636e0ae6fd0cd7dd03eac5edc2fb2c214528728 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 14 Jul 2026 07:39:13 +0200 Subject: [PATCH 67/71] Release 3.26.0 (#7326) --- 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 c012d540c..35c87f60f 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.26.0 - 2026-07-14 ### Added diff --git a/package-lock.json b/package-lock.json index 9cc8f9160..00128128e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.25.0", + "version": "3.26.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.25.0", + "version": "3.26.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index c484e3617..384ca8a8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.25.0", + "version": "3.26.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From b4a12341c5971f0019caed9e5f2e96b6a4c04ccf Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:26:28 +0700 Subject: [PATCH 68/71] Task/improve type safety in treemap chart component (#7325) * fix(ui): resolve type errors * feat(ui): enforce encapsulation * feat(ui): implement viewChild signal * feat(ui): implement output signal * feat(ui): implement input signal --- .../home-holdings/home-holdings.component.ts | 8 +- .../treemap-chart/treemap-chart.component.ts | 74 ++++++++++--------- 2 files changed, 42 insertions(+), 40 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 cf7e52833..bae154607 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 @@ -58,7 +58,7 @@ export class GfHomeHoldingsComponent implements OnInit { protected hasImpersonationId: boolean; protected hasPermissionToAccessHoldingsChart: boolean; protected hasPermissionToCreateActivity: boolean; - protected holdings: PortfolioPosition[]; + protected holdings: PortfolioPosition[] | undefined; protected holdingType: HoldingType = 'ACTIVE'; protected readonly holdingTypeOptions: ToggleOption[] = [ { label: $localize`Active`, value: 'ACTIVE' }, @@ -181,8 +181,8 @@ export class GfHomeHoldingsComponent implements OnInit { this.viewModeFormControl.setValue( this.deviceType === 'mobile' ? GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE - : this.user?.settings?.holdingsViewMode || - GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE, + : (this.user?.settings?.holdingsViewMode ?? + GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE), { emitEvent: false } ); } else if (this.holdingType === 'CLOSED') { @@ -192,7 +192,7 @@ export class GfHomeHoldingsComponent implements OnInit { ); } - this.holdings = []; + this.holdings = undefined; this.fetchHoldings() .pipe(takeUntilDestroyed(this.destroyRef)) diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index fd4e63f29..eb0265af3 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -15,18 +15,16 @@ import { ChangeDetectionStrategy, Component, ElementRef, - EventEmitter, - Input, + input, OnChanges, OnDestroy, - Output, - ViewChild + output, + viewChild } from '@angular/core'; import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; import type { ChartData, TooltipOptions } from 'chart.js'; -import { LinearScale } from 'chart.js'; -import { Chart, Tooltip } from 'chart.js'; +import { Chart, LinearScale, Tooltip } from 'chart.js'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; import { isUUID } from 'class-validator'; import { differenceInDays, max } from 'date-fns'; @@ -52,33 +50,31 @@ const { gray, green, red } = OpenColor; export class GfTreemapChartComponent implements AfterViewInit, OnChanges, OnDestroy { - @Input() baseCurrency: string; - @Input() colorScheme: ColorScheme; - @Input() cursor: string; - @Input() dateRange: DateRange; - @Input() holdings: PortfolioPosition[]; - @Input() locale = getLocale(); + public readonly baseCurrency = input.required(); + public readonly colorScheme = input.required(); + public readonly cursor = input.required(); + public readonly dateRange = input.required(); + public readonly holdings = input(); + public readonly locale = input(getLocale()); - @Output() treemapChartClicked = new EventEmitter(); + public readonly treemapChartClicked = output(); - @ViewChild('chartCanvas') chartCanvas: ElementRef; + protected isLoading = true; - public chart: Chart<'treemap'>; - public isLoading = true; + private chart: Chart<'treemap'>; + private readonly chartCanvas = + viewChild.required>('chartCanvas'); public constructor() { Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement); } + public ngAfterViewInit() { - if (this.holdings) { - this.initialize(); - } + this.initialize(); } public ngOnChanges() { - if (this.holdings) { - this.initialize(); - } + this.initialize(); } public ngOnDestroy() { @@ -159,13 +155,19 @@ export class GfTreemapChartComponent } private initialize() { + const holdings = this.holdings(); + + if (!holdings) { + return; + } + this.isLoading = true; const { endDate, startDate } = getIntervalFromDateRange({ - dateRange: this.dateRange + dateRange: this.dateRange() }); - const netPerformancePercentsWithCurrencyEffect = this.holdings.map( + const netPerformancePercentsWithCurrencyEffect = holdings.map( ({ dateOfFirstActivity, netPerformancePercentWithCurrencyEffect }) => { return getAnnualizedPerformancePercent({ daysInMarket: differenceInDays( @@ -301,12 +303,12 @@ export class GfTreemapChartComponent }, spacing: 1, // @ts-expect-error: should be PortfolioPosition[] - tree: this.holdings + tree: this.holdings() } ] }; - if (this.chartCanvas) { + if (this.chartCanvas()) { if (this.chart) { this.chart.data = data; this.chart.options.plugins ??= {}; @@ -315,7 +317,7 @@ export class GfTreemapChartComponent this.chart.update(); } else { - this.chart = new Chart<'treemap'>(this.chartCanvas.nativeElement, { + this.chart = new Chart<'treemap'>(this.chartCanvas().nativeElement, { data, options: { animation: false, @@ -339,9 +341,9 @@ export class GfTreemapChartComponent } catch {} }, onHover: (event, chartElement) => { - if (this.cursor) { + if (this.cursor()) { (event.native?.target as HTMLElement).style.cursor = - chartElement[0] ? this.cursor : 'default'; + chartElement[0] ? this.cursor() : 'default'; } }, plugins: { @@ -359,9 +361,9 @@ export class GfTreemapChartComponent private getTooltipPluginConfiguration(): Partial> { return { ...getTooltipOptions({ - colorScheme: this.colorScheme, - currency: this.baseCurrency, - locale: this.locale + colorScheme: this.colorScheme(), + currency: this.baseCurrency(), + locale: this.locale() }), // @ts-expect-error: no need to set all attributes in callbacks callbacks: { @@ -381,19 +383,19 @@ export class GfTreemapChartComponent return [ `${name ?? symbol} (${allocationInPercentage})`, - `${value?.toLocaleString(this.locale, { + `${value?.toLocaleString(this.locale(), { maximumFractionDigits: 2, minimumFractionDigits: 2 - })} ${this.baseCurrency}`, + })} ${this.baseCurrency()}`, '', $localize`Change` + ' (' + $localize`Performance` + ')', `${sign}${raw._data.netPerformanceWithCurrencyEffect.toLocaleString( - this.locale, + this.locale(), { maximumFractionDigits: 2, minimumFractionDigits: 2 } - )} ${this.baseCurrency} (${netPerformanceInPercentageWithSign})` + )} ${this.baseCurrency()} (${netPerformanceInPercentageWithSign})` ]; } else { return [ From 45ef139cf8493d45c2dac094b3b53342f5db5866 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:19:01 +0700 Subject: [PATCH 69/71] Task/enable @typescript-eslint/prefer-nullish-coalescing rule in libs/ui and apps/client (#7317) * feat(ui): implement prefer-nullish-coalescing eslint check * feat(client): implement prefer-nullish-coalescing eslint check * fix(client): remove whitespace --- apps/client/eslint.config.cjs | 4 +++- apps/client/jest.config.ts | 1 - .../asset-profile-dialog.component.ts | 10 +++++----- .../create-or-update-account-dialog.component.ts | 4 ++-- .../create-or-update-activity-dialog.component.ts | 2 +- apps/client/src/app/services/user/user.service.ts | 2 +- libs/ui/eslint.config.cjs | 3 ++- .../lib/activities-table/activities-table.component.ts | 2 +- 8 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/client/eslint.config.cjs b/apps/client/eslint.config.cjs index 96ecefd50..bcbe2c1c1 100644 --- a/apps/client/eslint.config.cjs +++ b/apps/client/eslint.config.cjs @@ -47,7 +47,9 @@ module.exports = [ { files: ['**/*.ts', '**/*.tsx'], // Override or add rules here - rules: {} + rules: { + '@typescript-eslint/prefer-nullish-coalescing': 'error' + } }, { files: ['**/*.js', '**/*.jsx'], diff --git a/apps/client/jest.config.ts b/apps/client/jest.config.ts index 04378bdbd..26d772e11 100644 --- a/apps/client/jest.config.ts +++ b/apps/client/jest.config.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ export default { displayName: 'client', diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 967a289ef..c34e8eb78 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -550,7 +550,7 @@ export class GfAssetProfileDialogComponent implements OnInit { ) as Record, locale: this.assetProfileForm.controls.scraperConfiguration.controls.locale - ?.value || undefined, + ?.value ?? undefined, mode: this.assetProfileForm.controls.scraperConfiguration.controls.mode ?.value ?? undefined, @@ -599,7 +599,7 @@ export class GfAssetProfileDialogComponent implements OnInit { assetClass: this.assetProfileForm.controls.assetClass.value ?? undefined, assetSubClass: this.assetProfileForm.controls.assetSubClass.value ?? undefined, - comment: this.assetProfileForm.controls.comment.value || undefined, + comment: this.assetProfileForm.controls.comment.value ?? undefined, currency: this.assetProfileForm.controls.currency.value ?? undefined, dataGatheringFrequency: this.assetProfileForm.controls.dataGatheringFrequency.value ?? @@ -607,8 +607,8 @@ export class GfAssetProfileDialogComponent implements OnInit { isActive: isBoolean(this.assetProfileForm.controls.isActive.value) ? this.assetProfileForm.controls.isActive.value : undefined, - name: this.assetProfileForm.controls.name.value || undefined, - url: this.assetProfileForm.controls.url.value || undefined + name: this.assetProfileForm.controls.name.value ?? undefined, + url: this.assetProfileForm.controls.url.value ?? undefined }; try { @@ -743,7 +743,7 @@ export class GfAssetProfileDialogComponent implements OnInit { ) as Record, locale: this.assetProfileForm.controls.scraperConfiguration.controls.locale - ?.value || undefined, + ?.value ?? undefined, mode: this.assetProfileForm.controls.scraperConfiguration.controls .mode?.value, selector: 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 ca88120ca..d27b5ceae 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 @@ -200,12 +200,12 @@ export class GfCreateOrUpdateAccountDialogComponent { protected async onSubmit() { const account: CreateAccountDto | UpdateAccountDto = { balance: this.accountForm.get('balance')?.value, - comment: this.accountForm.get('comment')?.value || null, + comment: this.accountForm.get('comment')?.value ?? null, currency: this.accountForm.get('currency')?.value, id: this.accountForm.get('accountId')?.value, isExcluded: this.accountForm.get('isExcluded')?.value, name: this.accountForm.get('name')?.value, - platformId: this.accountForm.get('platformId')?.value?.id || null, + platformId: this.accountForm.get('platformId')?.value?.id ?? null, tags: this.accountForm .get('tags') ?.value?.filter(({ id }: Tag) => { 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 be9ac7b04..10bc7ccc8 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 @@ -493,7 +493,7 @@ export class GfCreateOrUpdateActivityDialogComponent { accountId: this.activityForm.get('accountId')?.value, assetClass: this.activityForm.get('assetClass')?.value, assetSubClass: this.activityForm.get('assetSubClass')?.value, - comment: this.activityForm.get('comment')?.value || null, + comment: this.activityForm.get('comment')?.value ?? null, currency: this.activityForm.get('currency')?.value, customCurrency: this.activityForm.get('currencyOfUnitPrice')?.value, dataSource: ['FEE', 'INTEREST', 'LIABILITY', 'VALUABLE'].includes( diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index 52f33dc5a..d58f77c3e 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -192,6 +192,6 @@ export class UserService extends ObservableStore { return throwError(errMessage); } - return throwError(error || 'Server error'); + return throwError(error ?? 'Server error'); } } diff --git a/libs/ui/eslint.config.cjs b/libs/ui/eslint.config.cjs index e21452b5f..65d17debd 100644 --- a/libs/ui/eslint.config.cjs +++ b/libs/ui/eslint.config.cjs @@ -41,7 +41,8 @@ module.exports = [ } ], '@angular-eslint/prefer-inject': 'off', - '@angular-eslint/prefer-standalone': 'off' + '@angular-eslint/prefer-standalone': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'error' }, languageOptions: { parserOptions: { diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index b1bfcf686..fba02ad30 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -267,7 +267,7 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit { public isExcludedFromAnalysis(activity: Activity) { return ( - (activity.account && isAccountExcluded(activity.account)) || + (activity.account && isAccountExcluded(activity.account)) ?? activity.tags?.some(({ id }) => { return id === TAG_ID_EXCLUDE_FROM_ANALYSIS; }) === true From d857bc882ca84e857f2c48c4fe14c8ce7cd97536 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:22:48 +0200 Subject: [PATCH 70/71] Task/add skeleton loader to coupons table of admin control panel (#7327) Add skeleton loader --- .../admin-overview/admin-overview.component.ts | 7 +++++++ .../app/components/admin-overview/admin-overview.html | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 7374fb05c..62bc78df1 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -63,6 +63,7 @@ import { trashOutline } from 'ionicons/icons'; import ms, { StringValue } from 'ms'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -79,6 +80,7 @@ import ms, { StringValue } from 'ms'; MatSnackBarModule, MatSlideToggleModule, MatTableModule, + NgxSkeletonLoaderModule, ReactiveFormsModule, RouterModule ], @@ -102,6 +104,7 @@ export class GfAdminOverviewComponent implements OnInit { protected hasPermissionToToggleReadOnlyMode: boolean; protected readonly info: InfoItem; protected isDataGatheringEnabled: boolean; + protected isLoading = false; protected readonly permissions = permissions; protected systemMessage: SystemMessage; protected userCount: number; @@ -320,6 +323,8 @@ export class GfAdminOverviewComponent implements OnInit { } private fetchAdminData() { + this.isLoading = true; + this.adminService .fetchAdminData() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -336,6 +341,8 @@ export class GfAdminOverviewComponent implements OnInit { this.userCount = userCount; this.version = version; + this.isLoading = false; + this.changeDetectorRef.markForCheck(); }); } diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html index 2fde94083..7e05600a6 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -248,6 +248,17 @@ mat-row > + + @if (isLoading) { + + }
From df5fa6a4b9ea50e81066f70d27f6130eb7d83535 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 14 Jul 2026 18:23:47 +0200 Subject: [PATCH 71/71] Task/refactor canDeleteUser() helper (#7324) Refactor canDeleteUser() --- .../components/admin-users/admin-users.component.ts | 2 ++ .../src/app/components/admin-users/admin-users.html | 7 ++++++- .../user-detail-dialog/user-detail-dialog.component.ts | 7 ++++++- .../user-detail-dialog/user-detail-dialog.html | 7 ++++++- libs/common/src/lib/helper.ts | 10 ++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) 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 4b9848cf8..305c48dcf 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 @@ -8,6 +8,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_LOCALE, DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { + canDeleteUser, getCountryName, getDateFnsLocale, getDateFormatString, @@ -91,6 +92,7 @@ export class GfAdminUsersComponent implements OnInit { >(); protected defaultDateFormat: string; protected displayedColumns: string[] = []; + protected readonly canDeleteUser = canDeleteUser; protected readonly getCountryName = getCountryName; protected readonly getEmojiFlag = getEmojiFlag; protected hasPermissionForSubscription: boolean; diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index 668f39557..7e7e5edeb 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -241,7 +241,12 @@