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