From 5954e586948a816678cd89161a36d2db4c17316c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 6 Nov 2025 08:25:47 +0100 Subject: [PATCH 001/157] Task/upgrade @ionic/angular to version 8.7.8 (#5909) * Upgrade @ionic/angular to version 8.7.8 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 47 ++++++++++++----------------------------------- package.json | 2 +- 3 files changed, 14 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ff17256..7f0afe895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Changed the build executor of the client from `@nx/angular:webpack-browser` to `@nx/angular:browser-esbuild` - Refactored the app component to standalone - Improved the language localization for German (`de`) +- Upgraded `@ionic/angular` from version `8.7.3` to `8.7.8` ### Fixed diff --git a/package-lock.json b/package-lock.json index 095b9f7f3..f66e02cdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.0", "@internationalized/number": "3.6.3", - "@ionic/angular": "8.7.3", + "@ionic/angular": "8.7.8", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", "@nestjs/cache-manager": "3.0.1", @@ -6015,12 +6015,12 @@ } }, "node_modules/@ionic/angular": { - "version": "8.7.3", - "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.7.3.tgz", - "integrity": "sha512-Fd2bsluwsi88d8AEvSVANn3a7xZ7NEmlvgVTLnuF9VTI0TgdkLQptgEolty00axnQdjCaxSXxgFJd/m0gVpKIg==", + "version": "8.7.8", + "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.7.8.tgz", + "integrity": "sha512-IBN5h3nIOwbuglLit48S7wNeg7NHtl/vaKAHDggICyzI92cSg5yYL07Fz59pszhkBlZQUB5SQnml990Zj2bZUg==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.3", + "@ionic/core": "8.7.8", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -6034,12 +6034,12 @@ } }, "node_modules/@ionic/core": { - "version": "8.7.3", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.3.tgz", - "integrity": "sha512-KdyMxpMDQj+uqpztpK6yvN/T96hqcDiGXQ4T+aAZ+LW3wV3+0it6/rbh9C1B/wCl4Isnm4IRltPabgEfNJ50nw==", + "version": "8.7.8", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.8.tgz", + "integrity": "sha512-GLWb/lz3kocpzTZTeQQ5xxoWz4CKHD6zpnbwJknTKsncebohAaw2KTe7uOw5toKQEDdohTseFuSGoDDBoRQ1Ug==", "license": "MIT", "dependencies": { - "@stencil/core": "4.36.2", + "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" } @@ -12995,9 +12995,9 @@ "license": "MIT" }, "node_modules/@stencil/core": { - "version": "4.36.2", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz", - "integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz", + "integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==", "license": "MIT", "bin": { "stencil": "bin/stencil" @@ -24942,29 +24942,6 @@ "@stencil/core": "^4.35.3" } }, - "node_modules/ionicons/node_modules/@stencil/core": { - "version": "4.36.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.3.tgz", - "integrity": "sha512-C9DOaAjm+hSYRuVoUuYWG/lrYT8+4DG0AL0m1Ea9+G5v2Y6ApVpNJLbXvFlRZIdDMGecH86s6v0Gp39uockLxg==", - "license": "MIT", - "bin": { - "stencil": "bin/stencil" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=7.10.0" - }, - "optionalDependencies": { - "@rollup/rollup-darwin-arm64": "4.34.9", - "@rollup/rollup-darwin-x64": "4.34.9", - "@rollup/rollup-linux-arm64-gnu": "4.34.9", - "@rollup/rollup-linux-arm64-musl": "4.34.9", - "@rollup/rollup-linux-x64-gnu": "4.34.9", - "@rollup/rollup-linux-x64-musl": "4.34.9", - "@rollup/rollup-win32-arm64-msvc": "4.34.9", - "@rollup/rollup-win32-x64-msvc": "4.34.9" - } - }, "node_modules/ioredis": { "version": "5.8.2", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", diff --git a/package.json b/package.json index ea8646565..b247cfcc0 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.0", "@internationalized/number": "3.6.3", - "@ionic/angular": "8.7.3", + "@ionic/angular": "8.7.8", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", "@nestjs/cache-manager": "3.0.1", From ef6310bc75218208e9c2a443e20e8d637015db13 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:30:35 +0100 Subject: [PATCH 002/157] Release 2.215.0 (#5922) --- 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 7f0afe895..4c3542ff5 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). -## 2.215.0-beta.1 - 2025-11-05 +## 2.215.0 - 2025-11-06 ### Added diff --git a/package-lock.json b/package-lock.json index f66e02cdd..2e32c7d2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.215.0-beta.1", + "version": "2.215.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.215.0-beta.1", + "version": "2.215.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index b247cfcc0..49d2978de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.215.0-beta.1", + "version": "2.215.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 87891976ab91a8afc43fe59e1607e8bda483272a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:55:53 +0100 Subject: [PATCH 003/157] Task/reorder lifecycle hooks in various components (#5919) * Reorder lifecycle hooks --- .../create-asset-profile-dialog.component.ts | 2 +- .../app/components/admin-platform/admin-platform.component.ts | 2 +- apps/client/src/app/components/admin-tag/admin-tag.component.ts | 2 +- .../create-watchlist-item-dialog.component.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index 18dc48c39..44a0b374b 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -53,7 +53,7 @@ import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; styleUrls: ['./create-asset-profile-dialog.component.scss'], templateUrl: 'create-asset-profile-dialog.html' }) -export class GfCreateAssetProfileDialogComponent implements OnInit, OnDestroy { +export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { public createAssetProfileForm: FormGroup; public ghostfolioPrefix = `${ghostfolioPrefix}_`; public mode: CreateAssetProfileDialogMode; diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 6c95cee0b..6642d2315 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -51,7 +51,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- styleUrls: ['./admin-platform.component.scss'], templateUrl: './admin-platform.component.html' }) -export class GfAdminPlatformComponent implements OnInit, OnDestroy { +export class GfAdminPlatformComponent implements OnDestroy, OnInit { @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index 5552fa01b..88e8faa9d 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -48,7 +48,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int styleUrls: ['./admin-tag.component.scss'], templateUrl: './admin-tag.component.html' }) -export class GfAdminTagComponent implements OnInit, OnDestroy { +export class GfAdminTagComponent implements OnDestroy, OnInit { @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts index 7bd7d2ae1..60d74be92 100644 --- a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts @@ -36,7 +36,7 @@ import { Subject } from 'rxjs'; styleUrls: ['./create-watchlist-item-dialog.component.scss'], templateUrl: 'create-watchlist-item-dialog.html' }) -export class GfCreateWatchlistItemDialogComponent implements OnInit, OnDestroy { +export class GfCreateWatchlistItemDialogComponent implements OnDestroy, OnInit { public createWatchlistItemForm: FormGroup; private unsubscribeSubject = new Subject(); From 4746a64d3be32d9c65af1d3833c2a792ecf6670a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:56:46 +0100 Subject: [PATCH 004/157] Task/upgrade chart.js to version 4.5.1 (#5905) * Upgrade chart.js to version 4.5.1 * Update changelog --- CHANGELOG.md | 6 ++++++ package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c3542ff5..6fc83484d 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 + +- Upgraded `chart.js` from version `4.5.0` to `4.5.1` + ## 2.215.0 - 2025-11-06 ### Added diff --git a/package-lock.json b/package-lock.json index 2e32c7d2b..ca3a9f30e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "big.js": "7.0.1", "bootstrap": "4.6.2", "bull": "4.16.5", - "chart.js": "4.5.0", + "chart.js": "4.5.1", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-treemap": "3.1.0", "chartjs-plugin-annotation": "3.1.0", @@ -17502,9 +17502,9 @@ "license": "MIT" }, "node_modules/chart.js": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", - "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" diff --git a/package.json b/package.json index 49d2978de..829eb2bde 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "big.js": "7.0.1", "bootstrap": "4.6.2", "bull": "4.16.5", - "chart.js": "4.5.0", + "chart.js": "4.5.1", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-treemap": "3.1.0", "chartjs-plugin-annotation": "3.1.0", From d1190fc15a6d9f49ddeb71c28c95fbae2459b4ba Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:57:01 +0100 Subject: [PATCH 005/157] Task/upgrade svgmap to version 2.14.0 (#5904) * Upgrade svgmap to version 2.14.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc83484d..8a503cc42 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 - Upgraded `chart.js` from version `4.5.0` to `4.5.1` +- Upgraded `svgmap` from version `2.12.2` to `2.14.0` ## 2.215.0 - 2025-11-06 diff --git a/package-lock.json b/package-lock.json index ca3a9f30e..c33710231 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,7 +86,7 @@ "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "18.5.0", - "svgmap": "2.12.2", + "svgmap": "2.14.0", "tablemark": "4.1.0", "twitter-api-v2": "1.27.0", "uuid": "11.1.0", @@ -38794,9 +38794,9 @@ "license": "BSD-2-Clause" }, "node_modules/svgmap": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.12.2.tgz", - "integrity": "sha512-SCX1Oys3v1dz3mTEbQha+6lrHGyu3LwXBhcgW0HlTh7waQDMFqNUKD8hADvDaPkPapRvNCLMnXaVD1Pbxbnhow==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.14.0.tgz", + "integrity": "sha512-+Vklx4DO1uv1SFq6wnJWl/dRjX4uRT9CcsIHuADxAcZ+h5X1OSyDVbNdIu837fx5TtYYuaGRhWuFCXIioN/1ww==", "license": "MIT", "dependencies": { "svg-pan-zoom": "^3.6.2" diff --git a/package.json b/package.json index 829eb2bde..ff7f05ea0 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "18.5.0", - "svgmap": "2.12.2", + "svgmap": "2.14.0", "tablemark": "4.1.0", "twitter-api-v2": "1.27.0", "uuid": "11.1.0", From 9383fc00cb61482e162df7fa6dcfcdbe32626b23 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 9 Nov 2025 07:44:53 +0100 Subject: [PATCH 006/157] Task/introduce interface for get account response (#5902) * Introduce interface for get account response --- apps/api/src/app/account/account.controller.ts | 8 +++----- apps/client/src/app/services/data.service.ts | 4 ++-- libs/common/src/lib/interfaces/index.ts | 2 ++ .../interfaces/responses/account-response.interface.ts | 3 +++ 4 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 libs/common/src/lib/interfaces/responses/account-response.interface.ts diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index 7b24ccdcb..cd6892ab8 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -9,13 +9,11 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation/imp import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { AccountBalancesResponse, + AccountResponse, AccountsResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; -import type { - AccountWithValue, - RequestWithUser -} from '@ghostfolio/common/types'; +import type { RequestWithUser } from '@ghostfolio/common/types'; import { Body, @@ -114,7 +112,7 @@ export class AccountController { public async getAccountById( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('id') id: string - ): Promise { + ): Promise { const impersonationUserId = await this.impersonationService.validateImpersonationId(impersonationId); diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 6f0b17ed1..f83746009 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -22,6 +22,7 @@ import { Access, AccessTokenResponse, AccountBalancesResponse, + AccountResponse, AccountsResponse, ActivitiesResponse, ActivityResponse, @@ -54,7 +55,6 @@ import { } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; import type { - AccountWithValue, AiPromptMode, DateRange, GroupBy @@ -186,7 +186,7 @@ export class DataService { } public fetchAccount(aAccountId: string) { - return this.http.get(`/api/v1/account/${aAccountId}`); + return this.http.get(`/api/v1/account/${aAccountId}`); } public fetchAccountBalances(aAccountId: string) { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 899813f30..5c516a4a6 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -36,6 +36,7 @@ import type { Position } from './position.interface'; import type { Product } from './product'; import type { AccessTokenResponse } from './responses/access-token-response.interface'; import type { AccountBalancesResponse } from './responses/account-balances-response.interface'; +import type { AccountResponse } from './responses/account-response.interface'; import type { AccountsResponse } from './responses/accounts-response.interface'; import type { ActivitiesResponse } from './responses/activities-response.interface'; import type { ActivityResponse } from './responses/activity-response.interface'; @@ -86,6 +87,7 @@ export { AccessTokenResponse, AccountBalance, AccountBalancesResponse, + AccountResponse, AccountsResponse, ActivitiesResponse, ActivityResponse, diff --git a/libs/common/src/lib/interfaces/responses/account-response.interface.ts b/libs/common/src/lib/interfaces/responses/account-response.interface.ts new file mode 100644 index 000000000..3e954dc72 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/account-response.interface.ts @@ -0,0 +1,3 @@ +import { AccountWithValue } from '@ghostfolio/common/types'; + +export interface AccountResponse extends AccountWithValue {} From 385d7f65629422b256d7ad55e6f960f032febc2b Mon Sep 17 00:00:00 2001 From: TMs Date: Tue, 11 Nov 2025 03:19:38 +0700 Subject: [PATCH 007/157] Feature/improve language localization for ZH 20251110 (#5928) * Improve language localization for ZH * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.zh.xlf | 118 ++++++++++++------------ 2 files changed, 60 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a503cc42..4a4adc311 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 +- Improved the language localization for Chinese (`zh`) - Upgraded `chart.js` from version `4.5.0` to `4.5.1` - Upgraded `svgmap` from version `2.12.2` to `2.14.0` diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 1595ea726..d5090164d 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -241,7 +241,7 @@ please - please + apps/client/src/app/pages/pricing/pricing-page.html 350 @@ -285,7 +285,7 @@ with - with + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 87 @@ -665,7 +665,7 @@ and is driven by the efforts of its contributors - and is driven by the efforts of its contributors + 并且得益于其 贡献者 apps/client/src/app/pages/about/overview/about-overview-page.html 49 @@ -965,7 +965,7 @@ and we share aggregated key metrics of the platform’s performance - and we share aggregated key metrics of the platform’s performance + 并且我们分享平台性能的聚合 关键指标 apps/client/src/app/pages/about/overview/about-overview-page.html 32 @@ -1181,7 +1181,7 @@ Activities - Activities + 活动 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 61 @@ -1233,7 +1233,7 @@ Current year - Current year + 当前年份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 204 @@ -1545,7 +1545,7 @@ The source code is fully available as open source software (OSS) under the AGPL-3.0 license - The source code is fully available as open source software (OSS) under the AGPL-3.0 license + 源代码完全可用,作为开源软件 (OSS),遵循AGPL-3.0许可证 apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -1609,7 +1609,7 @@ Current week - Current week + 当前周 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 196 @@ -2517,7 +2517,7 @@ for - for + 用于 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 128 @@ -2953,7 +2953,7 @@ per week - per week + 每周 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 130 @@ -3129,7 +3129,7 @@ Edit access - Edit access + 编辑权限 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 11 @@ -3233,7 +3233,7 @@ Get access to 80’000+ tickers from over 50 exchanges - Get access to 80’000+ tickers from over 50 exchanges + 获取来自 50 多个交易所的 80,000 多个行情的访问权限 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 84 @@ -3369,7 +3369,7 @@ less than - less than + 少于 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -3433,7 +3433,7 @@ Ghostfolio Status - Ghostfolio Status + Ghostfolio 状态 apps/client/src/app/pages/about/overview/about-overview-page.html 62 @@ -3441,7 +3441,7 @@ with your university e-mail address - with your university e-mail address + 使用您的学校电子邮件地址 apps/client/src/app/pages/pricing/pricing-page.html 365 @@ -3473,7 +3473,7 @@ and a safe withdrawal rate (SWR) of - and a safe withdrawal rate (SWR) of + 和安全取款率 (SWR) 为 apps/client/src/app/pages/portfolio/fire/fire-page.html 107 @@ -3497,7 +3497,7 @@ Job ID - Job ID + 作业 ID apps/client/src/app/components/admin-jobs/admin-jobs.html 34 @@ -3709,7 +3709,7 @@ or start a discussion at - or start a discussion at + 或在以下位置开始讨论 apps/client/src/app/pages/about/overview/about-overview-page.html 94 @@ -3897,7 +3897,7 @@ Exclude from Analysis - Exclude from Analysis + 排除在分析之外 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 90 @@ -3921,7 +3921,7 @@ Latest activities - Latest activities + 最新活动 apps/client/src/app/pages/public/public-page.html 211 @@ -3989,7 +3989,7 @@ Looking for a student discount? - Looking for a student discount? + 寻找学生折扣? apps/client/src/app/pages/pricing/pricing-page.html 359 @@ -4165,7 +4165,7 @@ Our official Ghostfolio Premium cloud offering is the easiest way to get started. Due to the time it saves, this will be the best option for most people. Revenue is used to cover operational costs for the hosting infrastructure and professional data providers, and to fund ongoing development. - 我们的官方 Ghostfolio Premium 云产品是最简单的入门方法。由于它节省了时间,这对于大多数人来说将是最佳选择。收入用于支付托管基础设施的成本和资助持续开发。 + 我们的官方 Ghostfolio Premium 云产品是最简单的入门方法。由于它节省了时间,这对于大多数人来说将是最佳选择。收入用于支付托管基础设施的成本和资助持续开发。 apps/client/src/app/pages/pricing/pricing-page.html 7 @@ -4365,7 +4365,7 @@ Sustainable retirement income - Sustainable retirement income + 可持续的退休收入 apps/client/src/app/pages/portfolio/fire/fire-page.html 40 @@ -4498,7 +4498,7 @@ per month - per month + 每月 apps/client/src/app/pages/portfolio/fire/fire-page.html 92 @@ -4514,7 +4514,7 @@ Website of Thomas Kaul - Website of Thomas Kaul + Thomas Kaul 的网站 apps/client/src/app/pages/about/overview/about-overview-page.html 44 @@ -4642,7 +4642,7 @@ User ID - User ID + 用户 ID apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 12 @@ -4762,7 +4762,7 @@ Request it - Request it + 请求它 apps/client/src/app/pages/pricing/pricing-page.html 361 @@ -4906,7 +4906,7 @@ , - , + , apps/client/src/app/pages/portfolio/fire/fire-page.html 93 @@ -4930,7 +4930,7 @@ contact us - contact us + 联系我们 apps/client/src/app/pages/pricing/pricing-page.html 353 @@ -5318,7 +5318,7 @@ View Details - View Details + 查看详细信息 apps/client/src/app/components/admin-users/admin-users.html 225 @@ -5526,7 +5526,7 @@ If you retire today, you would be able to withdraw - If you retire today, you would be able to withdraw + 如果您今天退休,您将能够提取 apps/client/src/app/pages/portfolio/fire/fire-page.html 66 @@ -5662,7 +5662,7 @@ Argentina - Argentina + 阿根廷 libs/ui/src/lib/i18n.ts 78 @@ -5734,7 +5734,7 @@ Close Holding - Close Holding + 关闭持仓 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 442 @@ -5774,7 +5774,7 @@ here - here + 这里 apps/client/src/app/pages/pricing/pricing-page.html 364 @@ -6011,7 +6011,7 @@ Indonesia - Indonesia + 印度尼西亚 libs/ui/src/lib/i18n.ts 90 @@ -6147,7 +6147,7 @@ Include in - Include in + 包含在 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 369 @@ -6427,7 +6427,7 @@ View Holding - View Holding + 查看持仓 libs/ui/src/lib/activities-table/activities-table.component.html 444 @@ -6571,7 +6571,7 @@ Oops! Could not update access. - Oops! Could not update access. + 哎呀!无法更新访问权限。 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 179 @@ -6579,7 +6579,7 @@ based on your total assets of - based on your total assets of + 基于您总资产的 apps/client/src/app/pages/portfolio/fire/fire-page.html 95 @@ -6695,7 +6695,7 @@ Role - Role + 角色 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 31 @@ -6711,7 +6711,7 @@ Accounts - Accounts + 账户 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 @@ -6743,7 +6743,7 @@ If you plan to open an account at - If you plan to open an account at + 如果您计划开通账户在 apps/client/src/app/pages/pricing/pricing-page.html 329 @@ -6775,7 +6775,7 @@ send an e-mail to - send an e-mail to + 发送电子邮件至 apps/client/src/app/pages/about/overview/about-overview-page.html 87 @@ -6855,7 +6855,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 + 使用我们的推荐链接并获得一年的Ghostfolio Premium会员资格 apps/client/src/app/pages/pricing/pricing-page.html 357 @@ -6935,7 +6935,7 @@ Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. - Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. + Ghostfolio 是一款轻量级的财富管理应用程序,旨在帮助个人跟踪股票、ETF 或加密货币,并做出基于数据的稳健投资决策。 apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7007,7 +7007,7 @@ Engagement per Day - Engagement per Day + 每日参与度 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 76 @@ -7153,7 +7153,7 @@ Country - Country + 国家 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 37 @@ -7261,7 +7261,7 @@ Check the system status at - Check the system status at + 检查系统状态 apps/client/src/app/pages/about/overview/about-overview-page.html 57 @@ -7309,7 +7309,7 @@ API Requests Today - API Requests Today + 今日 API 请求 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 86 @@ -7417,7 +7417,7 @@ The project has been initiated by - The project has been initiated by + 该项目发起于 apps/client/src/app/pages/about/overview/about-overview-page.html 40 @@ -7525,7 +7525,7 @@ Find account, holding or page... - Find account, holding or page... + 查找账户、持仓或页面... libs/ui/src/lib/assistant/assistant.component.ts 152 @@ -7941,7 +7941,7 @@ Current month - Current month + 当前月份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts 200 @@ -8126,7 +8126,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_ - 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 Slack 社区,发布到 @ghostfolio_ apps/client/src/app/pages/about/overview/about-overview-page.html 69 @@ -8250,7 +8250,7 @@ Liquidity - Liquidity + 流动性 apps/client/src/app/pages/i18n/i18n-page.html 70 @@ -8258,7 +8258,7 @@ Buying Power - Buying Power + 购买力 apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8266,7 +8266,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Your buying power is below ${thresholdMin} ${baseCurrency} + 您的购买力低于 ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8274,7 +8274,7 @@ Your buying power is 0 ${baseCurrency} - Your buying power is 0 ${baseCurrency} + 您的购买力为 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8282,7 +8282,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Your buying power exceeds ${thresholdMin} ${baseCurrency} + 您的购买力超过了 ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8586,7 +8586,7 @@ Registration Date - Registration Date + 注册日期 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 23 From 9b4392eee094c225969a1a428728cbdeddc357f9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:09:14 +0100 Subject: [PATCH 008/157] Task/improve localization of limited offer (#5929) * Improve localization --- .../user-account-membership/user-account-membership.html | 7 +++++-- apps/client/src/app/pages/pricing/pricing-page.html | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.html b/apps/client/src/app/components/user-account-membership/user-account-membership.html index eadf85612..321efbcdd 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.html +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -37,8 +37,11 @@
- Limited Offer! Get - {{ durationExtension }} extra + Limited Offer! +   + Get {{ durationExtension }} extra
} diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index bea55f47f..41af9f277 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -310,6 +310,7 @@ class="badge badge-warning font-weight-normal line-height-1 p-3 w-100" > Limited Offer! +   Get {{ durationExtension }} extra From da71ee73d0b2b355d4f4d9d9ed4b237009f97fad Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:09:41 +0100 Subject: [PATCH 009/157] Task/improve promotion system (#5930) * Add fallback to promotion logic --- apps/client/src/app/app.component.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index b70850016..de82c7d9c 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -110,10 +110,6 @@ export class GfAppComponent implements OnDestroy, OnInit { this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.info = this.dataService.fetchInfo(); - this.hasPromotion = - !!this.info?.subscriptionOffer?.coupon || - !!this.info?.subscriptionOffer?.durationExtension; - this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntil(this.unsubscribeSubject)) @@ -217,9 +213,11 @@ export class GfAppComponent implements OnDestroy, OnInit { this.hasInfoMessage = this.canCreateAccount || !!this.user?.systemMessage; - this.hasPromotion = - !!this.user?.subscription?.offer?.coupon || - !!this.user?.subscription?.offer?.durationExtension; + this.hasPromotion = this.user + ? !!this.user.subscription?.offer?.coupon || + !!this.user.subscription?.offer?.durationExtension + : !!this.info?.subscriptionOffer?.coupon || + !!this.info?.subscriptionOffer?.durationExtension; this.initializeTheme(this.user?.settings.colorScheme); From 332216ae1c90ece5a6ebfe31efc41e256808e7be Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:10:42 +0100 Subject: [PATCH 010/157] Task/refactor primary text colors (#5900) * Refactor primary text colors --- apps/client/src/styles.scss | 9 +++++++-- apps/client/src/styles/variables.scss | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss index 6c9742f23..b7a031bfa 100644 --- a/apps/client/src/styles.scss +++ b/apps/client/src/styles.scss @@ -1,5 +1,6 @@ @import './styles/bootstrap'; @import './styles/table'; +@import './styles/variables'; @import 'svgmap/dist/svgMap'; @@ -8,14 +9,18 @@ --font-family-sans-serif: 'Inter', Roboto, 'Helvetica Neue', sans-serif; --light-background: rgb(255, 255, 255); - --dark-primary-text: 0, 0, 0, 0.87; + --dark-primary-text: + #{red($dark-primary-text)}, #{green($dark-primary-text)}, + #{blue($dark-primary-text)}, #{alpha($dark-primary-text)}; --dark-secondary-text: 0, 0, 0, 0.54; --dark-accent-text: 0, 0, 0, 0.87; --dark-warn-text: 0, 0, 0, 0.87; --dark-disabled-text: 0, 0, 0, 0.38; --dark-dividers: 0, 0, 0, 0.12; --dark-focused: 0, 0, 0, 0.12; - --light-primary-text: 255, 255, 255, 1; + --light-primary-text: + #{red($light-primary-text)}, #{green($light-primary-text)}, + #{blue($light-primary-text)}, #{alpha($light-primary-text)}; --light-secondary-text: 255, 255, 255, 0.7; --light-accent-text: 255, 255, 255, 1; --light-warn-text: 255, 255, 255, 1; diff --git a/apps/client/src/styles/variables.scss b/apps/client/src/styles/variables.scss index dcf26eecc..061c182fd 100644 --- a/apps/client/src/styles/variables.scss +++ b/apps/client/src/styles/variables.scss @@ -1,4 +1,4 @@ $dark-primary-text: rgba(black, 0.87); -$light-primary-text: white; +$light-primary-text: rgba(white, 1); $mat-css-dark-theme-selector: '.theme-dark'; From 4c3b95353d058c173ac70425f3b551410c895868 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 10 Nov 2025 22:16:08 +0100 Subject: [PATCH 011/157] Release 2.216.0 (#5932) --- 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 4a4adc311..bc0ef2df9 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 +## 2.216.0 - 2025-11-10 ### Changed diff --git a/package-lock.json b/package-lock.json index c33710231..e44d8a513 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.215.0", + "version": "2.216.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.215.0", + "version": "2.216.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index ff7f05ea0..cc151f19c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.215.0", + "version": "2.216.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From e7270bfee3dd6a94be031fe8c2ae1254cca9ecfd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 11 Nov 2025 20:19:46 +0100 Subject: [PATCH 012/157] Task/improve localization of auto-renewal (#5933) * Improve localization --- .../user-account-membership/user-account-membership.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.html b/apps/client/src/app/components/user-account-membership/user-account-membership.html index 321efbcdd..351d5608a 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.html +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -70,7 +70,7 @@ } @else {
- No auto-renewal. + No auto-renewal on membership.
} From 9f878c42f4ec39673d1c757c30f0b6900385af67 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 11 Nov 2025 20:20:16 +0100 Subject: [PATCH 013/157] Task/refactor getHolding() in portfolio service (#5898) * Refactor getHolding() if no holding has been found * Update changelog --- CHANGELOG.md | 6 + apps/api/src/app/import/import.service.ts | 19 +- .../src/app/portfolio/portfolio.service.ts | 421 ++++++------------ 3 files changed, 166 insertions(+), 280 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc0ef2df9..50e770f2c 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 + +- Refactored the get holding functionality in the portfolio service + ## 2.216.0 - 2025-11-10 ### Changed diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 2ec28365e..669432db5 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -58,13 +58,18 @@ export class ImportService { userId }: AssetProfileIdentifier & { userId: string }): Promise { try { - const { activities, firstBuyDate, historicalData } = - await this.portfolioService.getHolding({ - dataSource, - symbol, - userId, - impersonationId: undefined - }); + const holding = await this.portfolioService.getHolding({ + dataSource, + symbol, + userId, + impersonationId: undefined + }); + + if (!holding) { + return []; + } + + const { activities, firstBuyDate, historicalData } = holding; const [[assetProfile], dividends] = await Promise.all([ this.symbolProfileService.getSymbolProfiles([ diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index b74b779f6..1ae6190e1 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -88,7 +88,6 @@ import { parseISO, set } from 'date-fns'; -import { isEmpty } from 'lodash'; import { PortfolioCalculator } from './calculator/portfolio-calculator'; import { PortfolioCalculatorFactory } from './calculator/portfolio-calculator.factory'; @@ -776,35 +775,7 @@ export class PortfolioService { }); if (activities.length === 0) { - return { - activities: [], - activitiesCount: 0, - averagePrice: undefined, - dataProviderInfo: undefined, - dividendInBaseCurrency: undefined, - dividendYieldPercent: undefined, - dividendYieldPercentWithCurrencyEffect: undefined, - feeInBaseCurrency: undefined, - firstBuyDate: undefined, - grossPerformance: undefined, - grossPerformancePercent: undefined, - grossPerformancePercentWithCurrencyEffect: undefined, - grossPerformanceWithCurrencyEffect: undefined, - historicalData: [], - investmentInBaseCurrencyWithCurrencyEffect: undefined, - marketPrice: undefined, - marketPriceMax: undefined, - marketPriceMin: undefined, - netPerformance: undefined, - netPerformancePercent: undefined, - netPerformancePercentWithCurrencyEffect: undefined, - netPerformanceWithCurrencyEffect: undefined, - performances: undefined, - quantity: undefined, - SymbolProfile: undefined, - tags: [], - value: undefined - }; + return undefined; } const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ @@ -818,7 +789,6 @@ export class PortfolioService { currency: userCurrency }); - const portfolioStart = portfolioCalculator.getStartDate(); const transactionPoints = portfolioCalculator.getTransactionPoints(); const { positions } = await portfolioCalculator.getSnapshot(); @@ -827,225 +797,108 @@ export class PortfolioService { return position.dataSource === dataSource && position.symbol === symbol; }); - if (holding) { - const { - averagePrice, - currency, - dividendInBaseCurrency, - fee, - firstBuyDate, - grossPerformance, - grossPerformancePercentage, - grossPerformancePercentageWithCurrencyEffect, - grossPerformanceWithCurrencyEffect, - investmentWithCurrencyEffect, - marketPrice, - netPerformance, - netPerformancePercentage, - netPerformancePercentageWithCurrencyEffectMap, - netPerformanceWithCurrencyEffectMap, - quantity, - tags, - timeWeightedInvestment, - timeWeightedInvestmentWithCurrencyEffect, - transactionCount - } = holding; - - const activitiesOfHolding = activities.filter(({ SymbolProfile }) => { - return ( - SymbolProfile.dataSource === dataSource && - SymbolProfile.symbol === symbol - ); - }); - - const dividendYieldPercent = getAnnualizedPerformancePercent({ - daysInMarket: differenceInDays(new Date(), parseDate(firstBuyDate)), - netPerformancePercentage: timeWeightedInvestment.eq(0) - ? new Big(0) - : dividendInBaseCurrency.div(timeWeightedInvestment) - }); - - const dividendYieldPercentWithCurrencyEffect = - getAnnualizedPerformancePercent({ - daysInMarket: differenceInDays(new Date(), parseDate(firstBuyDate)), - netPerformancePercentage: timeWeightedInvestmentWithCurrencyEffect.eq( - 0 - ) - ? new Big(0) - : dividendInBaseCurrency.div( - timeWeightedInvestmentWithCurrencyEffect - ) - }); + if (!holding) { + return undefined; + } - const historicalData = await this.dataProviderService.getHistorical( - [{ dataSource, symbol }], - 'day', - parseISO(firstBuyDate), - new Date() - ); + const { + averagePrice, + currency, + dividendInBaseCurrency, + fee, + firstBuyDate, + grossPerformance, + grossPerformancePercentage, + grossPerformancePercentageWithCurrencyEffect, + grossPerformanceWithCurrencyEffect, + investmentWithCurrencyEffect, + marketPrice, + netPerformance, + netPerformancePercentage, + netPerformancePercentageWithCurrencyEffectMap, + netPerformanceWithCurrencyEffectMap, + quantity, + tags, + timeWeightedInvestment, + timeWeightedInvestmentWithCurrencyEffect, + transactionCount + } = holding; - const historicalDataArray: HistoricalDataItem[] = []; - let marketPriceMax = Math.max( - activitiesOfHolding[0].unitPriceInAssetProfileCurrency, - marketPrice - ); - let marketPriceMaxDate = - marketPrice > activitiesOfHolding[0].unitPriceInAssetProfileCurrency - ? new Date() - : activitiesOfHolding[0].date; - let marketPriceMin = Math.min( - activitiesOfHolding[0].unitPriceInAssetProfileCurrency, - marketPrice + const activitiesOfHolding = activities.filter(({ SymbolProfile }) => { + return ( + SymbolProfile.dataSource === dataSource && + SymbolProfile.symbol === symbol ); + }); - if (historicalData[symbol]) { - let j = -1; - for (const [date, { marketPrice }] of Object.entries( - historicalData[symbol] - )) { - while ( - j + 1 < transactionPoints.length && - !isAfter(parseDate(transactionPoints[j + 1].date), parseDate(date)) - ) { - j++; - } - - let currentAveragePrice = 0; - let currentQuantity = 0; + const dividendYieldPercent = getAnnualizedPerformancePercent({ + daysInMarket: differenceInDays(new Date(), parseDate(firstBuyDate)), + netPerformancePercentage: timeWeightedInvestment.eq(0) + ? new Big(0) + : dividendInBaseCurrency.div(timeWeightedInvestment) + }); - const currentSymbol = transactionPoints[j]?.items.find( - (transactionPointSymbol) => { - return transactionPointSymbol.symbol === symbol; - } - ); + const dividendYieldPercentWithCurrencyEffect = + getAnnualizedPerformancePercent({ + daysInMarket: differenceInDays(new Date(), parseDate(firstBuyDate)), + netPerformancePercentage: timeWeightedInvestmentWithCurrencyEffect.eq(0) + ? new Big(0) + : dividendInBaseCurrency.div(timeWeightedInvestmentWithCurrencyEffect) + }); - if (currentSymbol) { - currentAveragePrice = currentSymbol.averagePrice.toNumber(); - currentQuantity = currentSymbol.quantity.toNumber(); - } + const historicalData = await this.dataProviderService.getHistorical( + [{ dataSource, symbol }], + 'day', + parseISO(firstBuyDate), + new Date() + ); - historicalDataArray.push({ - date, - averagePrice: currentAveragePrice, - marketPrice: - historicalDataArray.length > 0 - ? marketPrice - : currentAveragePrice, - quantity: currentQuantity - }); + const historicalDataArray: HistoricalDataItem[] = []; + let marketPriceMax = Math.max( + activitiesOfHolding[0].unitPriceInAssetProfileCurrency, + marketPrice + ); + let marketPriceMaxDate = + marketPrice > activitiesOfHolding[0].unitPriceInAssetProfileCurrency + ? new Date() + : activitiesOfHolding[0].date; + let marketPriceMin = Math.min( + activitiesOfHolding[0].unitPriceInAssetProfileCurrency, + marketPrice + ); - if (marketPrice > marketPriceMax) { - marketPriceMax = marketPrice; - marketPriceMaxDate = parseISO(date); - } - marketPriceMin = Math.min( - marketPrice ?? Number.MAX_SAFE_INTEGER, - marketPriceMin - ); + if (historicalData[symbol]) { + let j = -1; + for (const [date, { marketPrice }] of Object.entries( + historicalData[symbol] + )) { + while ( + j + 1 < transactionPoints.length && + !isAfter(parseDate(transactionPoints[j + 1].date), parseDate(date)) + ) { + j++; } - } else { - // Add historical entry for buy date, if no historical data available - historicalDataArray.push({ - averagePrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, - date: firstBuyDate, - marketPrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, - quantity: activitiesOfHolding[0].quantity - }); - } - const performancePercent = - this.benchmarkService.calculateChangeInPercentage( - marketPriceMax, - marketPrice - ); + let currentAveragePrice = 0; + let currentQuantity = 0; - return { - firstBuyDate, - marketPrice, - marketPriceMax, - marketPriceMin, - SymbolProfile, - tags, - activities: activitiesOfHolding, - activitiesCount: transactionCount, - averagePrice: averagePrice.toNumber(), - dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], - dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), - dividendYieldPercent: dividendYieldPercent.toNumber(), - dividendYieldPercentWithCurrencyEffect: - dividendYieldPercentWithCurrencyEffect.toNumber(), - feeInBaseCurrency: this.exchangeRateDataService.toCurrency( - fee.toNumber(), - SymbolProfile.currency, - userCurrency - ), - grossPerformance: grossPerformance?.toNumber(), - grossPerformancePercent: grossPerformancePercentage?.toNumber(), - grossPerformancePercentWithCurrencyEffect: - grossPerformancePercentageWithCurrencyEffect?.toNumber(), - grossPerformanceWithCurrencyEffect: - grossPerformanceWithCurrencyEffect?.toNumber(), - historicalData: historicalDataArray, - investmentInBaseCurrencyWithCurrencyEffect: - investmentWithCurrencyEffect?.toNumber(), - netPerformance: netPerformance?.toNumber(), - netPerformancePercent: netPerformancePercentage?.toNumber(), - netPerformancePercentWithCurrencyEffect: - netPerformancePercentageWithCurrencyEffectMap?.['max']?.toNumber(), - netPerformanceWithCurrencyEffect: - netPerformanceWithCurrencyEffectMap?.['max']?.toNumber(), - performances: { - allTimeHigh: { - performancePercent, - date: marketPriceMaxDate + const currentSymbol = transactionPoints[j]?.items.find( + (transactionPointSymbol) => { + return transactionPointSymbol.symbol === symbol; } - }, - quantity: quantity.toNumber(), - value: this.exchangeRateDataService.toCurrency( - quantity.mul(marketPrice ?? 0).toNumber(), - currency, - userCurrency - ) - }; - } else { - const currentData = await this.dataProviderService.getQuotes({ - user, - items: [{ symbol, dataSource: DataSource.YAHOO }] - }); - const marketPrice = currentData[symbol]?.marketPrice; - - let historicalData = await this.dataProviderService.getHistorical( - [{ symbol, dataSource: DataSource.YAHOO }], - 'day', - portfolioStart, - new Date() - ); + ); - if (isEmpty(historicalData)) { - try { - historicalData = await this.dataProviderService.getHistoricalRaw({ - assetProfileIdentifiers: [{ symbol, dataSource: DataSource.YAHOO }], - from: portfolioStart, - to: new Date() - }); - } catch { - historicalData = { - [symbol]: {} - }; + if (currentSymbol) { + currentAveragePrice = currentSymbol.averagePrice.toNumber(); + currentQuantity = currentSymbol.quantity.toNumber(); } - } - - const historicalDataArray: HistoricalDataItem[] = []; - let marketPriceMax = marketPrice; - let marketPriceMaxDate = new Date(); - let marketPriceMin = marketPrice; - for (const [date, { marketPrice }] of Object.entries( - historicalData[symbol] - )) { historicalDataArray.push({ date, - value: marketPrice + averagePrice: currentAveragePrice, + marketPrice: + historicalDataArray.length > 0 ? marketPrice : currentAveragePrice, + quantity: currentQuantity }); if (marketPrice > marketPriceMax) { @@ -1057,48 +910,70 @@ export class PortfolioService { marketPriceMin ); } + } else { + // Add historical entry for buy date, if no historical data available + historicalDataArray.push({ + averagePrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, + date: firstBuyDate, + marketPrice: activitiesOfHolding[0].unitPriceInAssetProfileCurrency, + quantity: activitiesOfHolding[0].quantity + }); + } - const performancePercent = - this.benchmarkService.calculateChangeInPercentage( - marketPriceMax, - marketPrice - ); - - return { - marketPrice, + const performancePercent = + this.benchmarkService.calculateChangeInPercentage( marketPriceMax, - marketPriceMin, - SymbolProfile, - activities: [], - activitiesCount: 0, - averagePrice: 0, - dataProviderInfo: undefined, - dividendInBaseCurrency: 0, - dividendYieldPercent: 0, - dividendYieldPercentWithCurrencyEffect: 0, - feeInBaseCurrency: 0, - firstBuyDate: undefined, - grossPerformance: undefined, - grossPerformancePercent: undefined, - grossPerformancePercentWithCurrencyEffect: undefined, - grossPerformanceWithCurrencyEffect: undefined, - historicalData: historicalDataArray, - investmentInBaseCurrencyWithCurrencyEffect: 0, - netPerformance: undefined, - netPerformancePercent: undefined, - netPerformancePercentWithCurrencyEffect: undefined, - netPerformanceWithCurrencyEffect: undefined, - performances: { - allTimeHigh: { - performancePercent, - date: marketPriceMaxDate - } - }, - quantity: 0, - tags: [], - value: 0 - }; - } + marketPrice + ); + + return { + firstBuyDate, + marketPrice, + marketPriceMax, + marketPriceMin, + SymbolProfile, + tags, + activities: activitiesOfHolding, + activitiesCount: transactionCount, + averagePrice: averagePrice.toNumber(), + dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], + dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), + dividendYieldPercent: dividendYieldPercent.toNumber(), + dividendYieldPercentWithCurrencyEffect: + dividendYieldPercentWithCurrencyEffect.toNumber(), + feeInBaseCurrency: this.exchangeRateDataService.toCurrency( + fee.toNumber(), + SymbolProfile.currency, + userCurrency + ), + grossPerformance: grossPerformance?.toNumber(), + grossPerformancePercent: grossPerformancePercentage?.toNumber(), + grossPerformancePercentWithCurrencyEffect: + grossPerformancePercentageWithCurrencyEffect?.toNumber(), + grossPerformanceWithCurrencyEffect: + grossPerformanceWithCurrencyEffect?.toNumber(), + historicalData: historicalDataArray, + investmentInBaseCurrencyWithCurrencyEffect: + investmentWithCurrencyEffect?.toNumber(), + netPerformance: netPerformance?.toNumber(), + netPerformancePercent: netPerformancePercentage?.toNumber(), + netPerformancePercentWithCurrencyEffect: + netPerformancePercentageWithCurrencyEffectMap?.['max']?.toNumber(), + netPerformanceWithCurrencyEffect: + netPerformanceWithCurrencyEffectMap?.['max']?.toNumber(), + performances: { + allTimeHigh: { + performancePercent, + date: marketPriceMaxDate + } + }, + quantity: quantity.toNumber(), + value: this.exchangeRateDataService.toCurrency( + quantity.mul(marketPrice ?? 0).toNumber(), + currency, + userCurrency + ) + }; } public async getPerformance({ From cca1590c2a3eb2383dcf251ff8df20959e5f99c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 21:07:31 +0100 Subject: [PATCH 014/157] Feature/update locales (#5931) * Update locales * Update translation * Update changelog --------- Co-authored-by: github-actions[bot] Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/client/src/locales/messages.ca.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.de.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.es.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.fr.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.it.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.nl.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.pl.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.pt.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.tr.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.uk.xlf | 52 ++++++++++++++----------- apps/client/src/locales/messages.xlf | 50 ++++++++++++++---------- apps/client/src/locales/messages.zh.xlf | 52 ++++++++++++++----------- 13 files changed, 360 insertions(+), 263 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50e770f2c..f176dd4af 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 - Refactored the get holding functionality in the portfolio service +- Improved the language localization for German (`de`) ## 2.216.0 - 2025-11-10 diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 7513eedaf..e75cabf4a 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -295,7 +295,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351
@@ -1486,6 +1486,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Implicació per Dia @@ -1995,7 +2003,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -2459,7 +2467,7 @@ Prova Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -2467,7 +2475,7 @@ Bescanviar el cupó apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -3344,7 +3352,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -3816,7 +3824,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -4372,7 +4380,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -4436,7 +4444,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -4788,7 +4796,7 @@ És gratuït. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5217,7 +5225,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5505,7 +5513,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -6745,7 +6753,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6788,14 +6796,6 @@ 69 - - No auto-renewal. - No auto-renewal. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year This year @@ -6857,7 +6857,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Limited Offer! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Get extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index a3583df02..9b515539c 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -42,7 +42,7 @@ bitte apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -669,6 +669,14 @@ 231 + + No auto-renewal on membership. + Keine automatische Erneuerung der Mitgliedschaft. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Engagement pro Tag @@ -726,7 +734,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -854,7 +862,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1230,7 +1238,7 @@ Premium ausprobieren apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -1238,7 +1246,7 @@ Gutschein einlösen apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2290,7 +2298,7 @@ kontaktiere uns apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -3058,7 +3066,7 @@ Suchst du nach einem Studentenrabatt? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3550,7 +3558,7 @@ Es ist kostenlos. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5264,7 +5272,7 @@ mit deiner Universitäts-E-Mail-Adresse apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5444,7 +5452,7 @@ Fordere ihn an apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5768,7 +5776,7 @@ hier apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6769,7 +6777,7 @@ Wenn du die Eröffnung eines Kontos planst bei apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6812,14 +6820,6 @@ 69 - - No auto-renewal. - Keine automatische Erneuerung. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Dieses Jahr @@ -6881,7 +6881,7 @@ um unseren Empfehlungslink zu verwenden und ein Ghostfolio Premium-Abonnement für ein Jahr zu erhalten apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Begrenztes Angebot! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Erhalte extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 62f437994..c70552d1f 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -43,7 +43,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -654,6 +654,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Contratación diaria @@ -711,7 +719,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -839,7 +847,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1215,7 +1223,7 @@ Prueba Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -1223,7 +1231,7 @@ Canjea el cupón apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2275,7 +2283,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -3035,7 +3043,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3535,7 +3543,7 @@ Es gratis. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5241,7 +5249,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5421,7 +5429,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5745,7 +5753,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6746,7 +6754,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6789,14 +6797,6 @@ 69 - - No auto-renewal. - Sin renovación automática. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Este año @@ -6858,7 +6858,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7910,6 +7910,10 @@ Limited Offer! ¡Oferta limitada! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7918,9 +7922,13 @@ Get extra Obtén extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 560859d05..af07071c6 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -34,7 +34,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -861,6 +861,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Engagement par Jour @@ -1106,7 +1114,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1494,7 +1502,7 @@ Essayer Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -1502,7 +1510,7 @@ Utiliser un Code Promotionnel apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2322,7 +2330,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -2514,7 +2522,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -2742,7 +2750,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -3534,7 +3542,7 @@ C’est gratuit. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5240,7 +5248,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5420,7 +5428,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5744,7 +5752,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6745,7 +6753,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6788,14 +6796,6 @@ 69 - - No auto-renewal. - Pas de renouvellement automatique. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Cette année @@ -6857,7 +6857,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Offre Limitée ! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Obtenez supplémentaires + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 076c02068..b5987e2b6 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -43,7 +43,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -654,6 +654,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Partecipazione giornaliera @@ -711,7 +719,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -839,7 +847,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1215,7 +1223,7 @@ Prova Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -1223,7 +1231,7 @@ Riscatta il buono apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2275,7 +2283,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -3035,7 +3043,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3535,7 +3543,7 @@ È gratuito. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5241,7 +5249,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5421,7 +5429,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5745,7 +5753,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6746,7 +6754,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6789,14 +6797,6 @@ 69 - - No auto-renewal. - No rinnovo automatico. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Anno corrente @@ -6858,7 +6858,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7910,6 +7910,10 @@ Limited Offer! Offerta limitata! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7918,9 +7922,13 @@ Get extra Get extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 4a17736b4..b88340f52 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -42,7 +42,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -653,6 +653,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Betrokkenheid per dag @@ -710,7 +718,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -838,7 +846,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1214,7 +1222,7 @@ Probeer Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -1222,7 +1230,7 @@ Coupon inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2274,7 +2282,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -3034,7 +3042,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3534,7 +3542,7 @@ Het is gratis. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5240,7 +5248,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5420,7 +5428,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5744,7 +5752,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6745,7 +6753,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6788,14 +6796,6 @@ 69 - - No auto-renewal. - Geen automatische verlenging. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Dit jaar @@ -6857,7 +6857,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Beperkt aanbod! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Krijg extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 321cfbecd..dddb4f79c 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -243,7 +243,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -1314,6 +1314,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Zaangażowanie na Dzień @@ -1691,7 +1699,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -2179,7 +2187,7 @@ Wypróbuj Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -2187,7 +2195,7 @@ Wykorzystaj kupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2979,7 +2987,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -3435,7 +3443,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -3983,7 +3991,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -4335,7 +4343,7 @@ Jest bezpłatny. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -4744,7 +4752,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -4888,7 +4896,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -5744,7 +5752,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6745,7 +6753,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6788,14 +6796,6 @@ 69 - - No auto-renewal. - Bez automatycznego odnawiania. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year W tym roku @@ -6857,7 +6857,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Oferta ograniczona czasowo! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Uzyskaj dodatkowo + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index dc8804544..fbbd51c47 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -34,7 +34,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -733,6 +733,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Envolvimento por Dia @@ -986,7 +994,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1482,7 +1490,7 @@ Experimentar Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -1490,7 +1498,7 @@ Resgatar Cupão apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2450,7 +2458,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -2642,7 +2650,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -3098,7 +3106,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3534,7 +3542,7 @@ É gratuito. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5240,7 +5248,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5420,7 +5428,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5744,7 +5752,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6745,7 +6753,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6788,14 +6796,6 @@ 69 - - No auto-renewal. - Sem renovação automática. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Este ano @@ -6857,7 +6857,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Limited Offer! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Get extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 235f670a3..9c3820229 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -215,7 +215,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -1182,6 +1182,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day Günlük etkileşim @@ -1551,7 +1559,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -2563,7 +2571,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -3475,7 +3483,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3827,7 +3835,7 @@ Ücretsiz. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -4336,7 +4344,7 @@ Premium’u Deneyin apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -4344,7 +4352,7 @@ Kupon Kullan apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -4584,7 +4592,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -5248,7 +5256,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -5420,7 +5428,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -5744,7 +5752,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6745,7 +6753,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6788,14 +6796,6 @@ 69 - - No auto-renewal. - Otomatik yenileme yok. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year Bu yıl @@ -6857,7 +6857,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7909,6 +7909,10 @@ Limited Offer! Sınırlı Teklif! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Get extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index c1f2c7bce..f34d576b2 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -295,7 +295,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -1527,7 +1527,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -1558,6 +1558,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Do you really want to delete this tag? Ви дійсно хочете видалити цей тег? @@ -1783,7 +1791,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -2751,7 +2759,7 @@ Спробуйте Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -2759,15 +2767,7 @@ Обміняти купон apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 - - - - No auto-renewal. - Без автоматичного поновлення. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 + 66 @@ -3636,7 +3636,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -4108,7 +4108,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -4700,7 +4700,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -4764,7 +4764,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -5156,7 +5156,7 @@ Це безкоштовно. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -5763,7 +5763,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -5955,7 +5955,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -6243,7 +6243,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -7909,6 +7909,10 @@ Limited Offer! Limited Offer! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7917,9 +7921,13 @@ Get extra Get extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 3a6ce2f09..1d8c395ad 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -228,7 +228,7 @@ please apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -1239,6 +1239,13 @@ 231 + + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day @@ -1583,7 +1590,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -2027,14 +2034,14 @@ Try Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 Redeem Coupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2763,7 +2770,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -3172,7 +3179,7 @@ with your university e-mail address apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -3665,7 +3672,7 @@ Looking for a student discount? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -3985,7 +3992,7 @@ It’s free. apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -4366,7 +4373,7 @@ Request it apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -4515,7 +4522,7 @@ contact us apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -5277,7 +5284,7 @@ here apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6155,7 +6162,7 @@ If you plan to open an account at apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6186,13 +6193,6 @@ 63 - - No auto-renewal. - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - From the beginning @@ -6240,7 +6240,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7167,6 +7167,10 @@ Limited Offer! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7174,9 +7178,13 @@ Get extra + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index d5090164d..4b5e3efd8 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -244,7 +244,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 350 + 351 @@ -1323,6 +1323,14 @@ 231 + + No auto-renewal on membership. + No auto-renewal on membership. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 73 + + Engagement per Day 每天的参与度 @@ -1700,7 +1708,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 343 + 344 apps/client/src/app/pages/register/register-page.html @@ -2188,7 +2196,7 @@ 尝试高级版 apps/client/src/app/components/user-account-membership/user-account-membership.html - 49 + 52 @@ -2196,7 +2204,7 @@ 兑换优惠券 apps/client/src/app/components/user-account-membership/user-account-membership.html - 63 + 66 @@ -2988,7 +2996,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 377 + 378 apps/client/src/app/pages/public/public-page.html @@ -3444,7 +3452,7 @@ 使用您的学校电子邮件地址 apps/client/src/app/pages/pricing/pricing-page.html - 365 + 366 @@ -3992,7 +4000,7 @@ 寻找学生折扣? apps/client/src/app/pages/pricing/pricing-page.html - 359 + 360 @@ -4344,7 +4352,7 @@ 免费。 apps/client/src/app/pages/pricing/pricing-page.html - 379 + 380 @@ -4765,7 +4773,7 @@ 请求它 apps/client/src/app/pages/pricing/pricing-page.html - 361 + 362 @@ -4933,7 +4941,7 @@ 联系我们 apps/client/src/app/pages/pricing/pricing-page.html - 353 + 354 @@ -5777,7 +5785,7 @@ 这里 apps/client/src/app/pages/pricing/pricing-page.html - 364 + 365 @@ -6746,7 +6754,7 @@ 如果您计划开通账户在 apps/client/src/app/pages/pricing/pricing-page.html - 329 + 330 @@ -6789,14 +6797,6 @@ 69 - - No auto-renewal. - 不自动续订。 - - apps/client/src/app/components/user-account-membership/user-account-membership.html - 70 - - This year 今年 @@ -6858,7 +6858,7 @@ 使用我们的推荐链接并获得一年的Ghostfolio Premium会员资格 apps/client/src/app/pages/pricing/pricing-page.html - 357 + 358 @@ -7910,6 +7910,10 @@ Limited Offer! 限时优惠! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 40 + apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -7918,9 +7922,13 @@ Get extra 获取额外 + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 43 + apps/client/src/app/pages/pricing/pricing-page.html - 314 + 315 From 54cc82e328fb2c8f845a86e03424bf6f74b1794d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 13 Nov 2025 20:27:16 +0100 Subject: [PATCH 015/157] Task/upgrade prisma to version 6.19.0 (#5937) * Upgrade prisma to version 6.19.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 72 +++++++++++++++++++++++------------------------ package.json | 4 +-- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f176dd4af..a46674ef3 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 - Refactored the get holding functionality in the portfolio service - Improved the language localization for German (`de`) +- Upgraded `prisma` from version `6.18.0` to `6.19.0` ## 2.216.0 - 2025-11-10 diff --git a/package-lock.json b/package-lock.json index e44d8a513..a1d1551e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "@nestjs/schedule": "6.0.1", "@nestjs/serve-static": "5.0.4", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.18.0", + "@prisma/client": "6.19.0", "@simplewebauthn/browser": "13.1.0", "@simplewebauthn/server": "13.1.1", "@stripe/stripe-js": "7.9.0", @@ -146,7 +146,7 @@ "nx": "21.5.1", "prettier": "3.6.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.18.0", + "prisma": "6.19.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", @@ -11883,9 +11883,9 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", - "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", + "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -11905,9 +11905,9 @@ } }, "node_modules/@prisma/config": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", - "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", + "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -11918,53 +11918,53 @@ } }, "node_modules/@prisma/debug": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", - "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", + "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", - "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", + "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/fetch-engine": "6.18.0", - "@prisma/get-platform": "6.18.0" + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/fetch-engine": "6.19.0", + "@prisma/get-platform": "6.19.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", - "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", + "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", + "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", - "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", + "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.18.0", - "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", - "@prisma/get-platform": "6.18.0" + "@prisma/debug": "6.19.0", + "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", + "@prisma/get-platform": "6.19.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", - "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", + "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.18.0" + "@prisma/debug": "6.19.0" } }, "node_modules/@redis/client": { @@ -35617,15 +35617,15 @@ } }, "node_modules/prisma": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", - "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", + "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.18.0", - "@prisma/engines": "6.18.0" + "@prisma/config": "6.19.0", + "@prisma/engines": "6.19.0" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index cc151f19c..5bf5e6775 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@nestjs/schedule": "6.0.1", "@nestjs/serve-static": "5.0.4", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.18.0", + "@prisma/client": "6.19.0", "@simplewebauthn/browser": "13.1.0", "@simplewebauthn/server": "13.1.1", "@stripe/stripe-js": "7.9.0", @@ -192,7 +192,7 @@ "nx": "21.5.1", "prettier": "3.6.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.18.0", + "prisma": "6.19.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", From 8d2fde35da6be7be72fb83cdf7949c4899e5f2dc Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:58:33 -0600 Subject: [PATCH 016/157] Task/fetch user data on demand in user detail dialog (#5923) * Fetch user data on demand in user detail dialog * Update changelog --- CHANGELOG.md | 1 + .../admin-users/admin-users.component.ts | 30 +++++--------- .../interfaces/interfaces.ts | 4 +- .../user-detail-dialog.component.ts | 33 ++++++++++++++-- .../user-detail-dialog.html | 39 +++++++------------ apps/client/src/app/services/admin.service.ts | 7 +++- 6 files changed, 63 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a46674ef3..4437d077c 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 - Refactored the get holding functionality in the portfolio service +- Changed the user data loading in the user detail dialog of the admin control panel’s users section to fetch data on demand - Improved the language localization for German (`de`) - Upgraded `prisma` from version `6.18.0` to `6.19.0` diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 94b5839c6..6b3335927 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,4 +1,12 @@ +import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; +import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; +import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; +import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; +import { DataService } from '@ghostfolio/client/services/data.service'; +import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { getDateFnsLocale, @@ -51,15 +59,6 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { ConfirmationDialogType } from '../../core/notification/confirmation-dialog/confirmation-dialog.type'; -import { NotificationService } from '../../core/notification/notification.service'; -import { AdminService } from '../../services/admin.service'; -import { DataService } from '../../services/data.service'; -import { ImpersonationStorageService } from '../../services/impersonation-storage.service'; -import { UserService } from '../../services/user/user.service'; -import { UserDetailDialogParams } from '../user-detail-dialog/interfaces/interfaces'; -import { GfUserDetailDialogComponent } from '../user-detail-dialog/user-detail-dialog.component'; - @Component({ imports: [ CommonModule, @@ -283,25 +282,16 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } private openUserDetailDialog(aUserId: string) { - const userData = this.dataSource.data.find(({ id }) => { - return id === aUserId; - }); - - if (!userData) { - this.router.navigate(['.'], { relativeTo: this.route }); - return; - } - const dialogRef = this.dialog.open< GfUserDetailDialogComponent, UserDetailDialogParams >(GfUserDetailDialogComponent, { autoFocus: false, data: { - userData, deviceType: this.deviceType, hasPermissionForSubscription: this.hasPermissionForSubscription, - locale: this.user?.settings?.locale + locale: this.user?.settings?.locale, + userId: aUserId }, height: this.deviceType === 'mobile' ? '98vh' : '60vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' diff --git a/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts index d29bc01bc..b922e7a54 100644 --- a/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts @@ -1,8 +1,6 @@ -import { AdminUsersResponse } from '@ghostfolio/common/interfaces'; - export interface UserDetailDialogParams { deviceType: string; hasPermissionForSubscription: boolean; locale: string; - userData: AdminUsersResponse['users'][0]; + userId: string; } 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 bd336c4f8..6dabf2f78 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 @@ -1,19 +1,24 @@ import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; +import { AdminUserResponse } from '@ghostfolio/common/interfaces'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, + ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, Inject, - OnDestroy + OnDestroy, + OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog'; -import { Subject } from 'rxjs'; +import { EMPTY, Subject } from 'rxjs'; +import { catchError, takeUntil } from 'rxjs/operators'; import { UserDetailDialogParams } from './interfaces/interfaces'; @@ -33,14 +38,36 @@ import { UserDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./user-detail-dialog.component.scss'], templateUrl: './user-detail-dialog.html' }) -export class GfUserDetailDialogComponent implements OnDestroy { +export class GfUserDetailDialogComponent implements OnDestroy, OnInit { + public user: AdminUserResponse; + private unsubscribeSubject = new Subject(); public constructor( + private adminService: AdminService, + private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, public dialogRef: MatDialogRef ) {} + public ngOnInit() { + this.adminService + .fetchUserById(this.data.userId) + .pipe( + takeUntil(this.unsubscribeSubject), + catchError(() => { + this.dialogRef.close(); + + return EMPTY; + }) + ) + .subscribe((user) => { + this.user = user; + + this.changeDetectorRef.markForCheck(); + }); + } + public onClose() { this.dialogRef.close(); } diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html index 6bc468b59..fcefee4f0 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -8,9 +8,7 @@
- - User ID - + User ID
Registration Date - Registration Date -
- - Role - + Role
@if (data.hasPermissionForSubscription) {
- - Country - + Country
}
@@ -46,20 +41,18 @@ i18n size="medium" [locale]="data.locale" - [value]="data.userData.accountCount" + [value]="user?.accountCount" + >Accounts - Accounts -
Activities - Activities -
@@ -71,20 +64,18 @@ size="medium" [locale]="data.locale" [precision]="0" - [value]="data.userData.engagement" + [value]="user?.engagement" + >Engagement per Day - Engagement per Day -
API Requests Today - API Requests Today -
} diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 2f3040ba3..cdac3ed38 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -8,11 +8,12 @@ import { } from '@ghostfolio/common/config'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { - AssetProfileIdentifier, AdminData, AdminJobs, AdminMarketData, + AdminUserResponse, AdminUsersResponse, + AssetProfileIdentifier, DataProviderGhostfolioStatusResponse, EnhancedSymbolProfile, Filter @@ -142,6 +143,10 @@ export class AdminService { return this.http.get('/api/v1/platform'); } + public fetchUserById(id: string) { + return this.http.get(`/api/v1/admin/user/${id}`); + } + public fetchUsers({ skip, take = DEFAULT_PAGE_SIZE From a57b670d7b9562524d9450d637628be6d12837e3 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:30:03 +0700 Subject: [PATCH 017/157] Task/enforce module boundaries for api and common modules (#5925) * feat(lint): allow circular self deps * feat(lint): enforce module boundaries * feat(lib): move data provider response interface to common * feat(lib): move symbol item interface to common * feat(lib): move activity interface to common * feat(lint): temporarily disable @nx/enforce-module-boundaries for ui files * feat(lint): temporarily disable @nx/enforce-module-boundaries for client files * feat(lint): ignore circular deps between client and ui * feat(common): implement barrel export for data provider response interface * feat(common): implement barrel export for activity interface * feat(common): implement barrel export for symbol item interface --- .../ghostfolio/ghostfolio.service.ts | 2 +- .../exchange-rate/exchange-rate.controller.ts | 2 +- apps/api/src/app/import/import.service.ts | 10 +++++----- .../portfolio-calculator.factory.ts | 7 +++++-- .../calculator/portfolio-calculator.ts | 2 +- ...tfolio-calculator-baln-buy-and-buy.spec.ts | 2 +- ...aln-buy-and-sell-in-two-activities.spec.ts | 2 +- ...folio-calculator-baln-buy-and-sell.spec.ts | 2 +- .../portfolio-calculator-baln-buy.spec.ts | 2 +- ...ulator-btceur-in-base-currency-eur.spec.ts | 3 +-- .../roai/portfolio-calculator-btceur.spec.ts | 3 +-- ...ator-btcusd-buy-and-sell-partially.spec.ts | 2 +- .../portfolio-calculator-btcusd-short.spec.ts | 3 +-- .../roai/portfolio-calculator-btcusd.spec.ts | 3 +-- .../roai/portfolio-calculator-fee.spec.ts | 2 +- .../portfolio-calculator-googl-buy.spec.ts | 2 +- .../portfolio-calculator-liability.spec.ts | 2 +- ...folio-calculator-msft-buy-and-sell.spec.ts | 2 +- ...-calculator-msft-buy-with-dividend.spec.ts | 2 +- ...ulator-novn-buy-and-sell-partially.spec.ts | 3 +-- ...folio-calculator-novn-buy-and-sell.spec.ts | 3 +-- .../portfolio-calculator-valuable.spec.ts | 2 +- .../interfaces/portfolio-order.interface.ts | 2 +- .../src/app/portfolio/portfolio.service.ts | 2 +- apps/api/src/app/symbol/symbol.controller.ts | 8 +++++--- apps/api/src/app/symbol/symbol.service.ts | 11 ++++------ .../alpha-vantage/alpha-vantage.service.ts | 6 ++---- .../coingecko/coingecko.service.ts | 6 ++---- .../data-provider/data-provider.service.ts | 6 ++---- .../eod-historical-data.service.ts | 6 ++---- .../financial-modeling-prep.service.ts | 6 ++---- .../ghostfolio/ghostfolio.service.ts | 6 ++---- .../google-sheets/google-sheets.service.ts | 6 ++---- .../interfaces/data-provider.interface.ts | 4 +--- .../data-provider/manual/manual.service.ts | 6 ++---- .../rapid-api/rapid-api.service.ts | 6 ++---- .../yahoo-finance/yahoo-finance.service.ts | 6 ++---- .../api/src/services/interfaces/interfaces.ts | 20 +------------------ .../account-detail-dialog.component.ts | 3 ++- .../asset-profile-dialog.component.ts | 1 + .../admin-platform.component.ts | 1 + ...ate-or-update-platform-dialog.component.ts | 1 + .../admin-tag/admin-tag.component.ts | 1 + .../create-or-update-tag-dialog.component.ts | 1 + .../app/components/header/header.component.ts | 1 + .../holding-detail-dialog.component.ts | 3 ++- .../src/app/components/rule/rule.component.ts | 1 + .../app/components/rules/rules.component.ts | 1 + ...reate-or-update-access-dialog.component.ts | 1 + .../user-account-access.component.ts | 1 + .../pages/accounts/accounts-page.component.ts | 1 + ...eate-or-update-account-dialog.component.ts | 1 + .../transfer-balance-dialog.component.ts | 1 + .../activities/activities-page.component.ts | 8 ++++++-- ...ate-or-update-activity-dialog.component.ts | 1 + .../interfaces/interfaces.ts | 3 +-- .../import-activities-dialog.component.ts | 4 ++-- .../portfolio/x-ray/x-ray-page.component.ts | 1 + apps/client/src/app/services/admin.service.ts | 3 ++- apps/client/src/app/services/data.service.ts | 5 +++-- .../app/services/import-activities.service.ts | 3 ++- .../src/app/services/web-authn.service.ts | 1 + eslint.config.cjs | 9 ++++++--- .../lib}/interfaces/activities.interface.ts | 0 libs/common/src/lib/interfaces/index.ts | 13 +++++++++++- .../activities-response.interface.ts | 2 +- .../responses/activity-response.interface.ts | 2 +- .../data-provider-response.interface.ts | 16 +++++++++++++++ .../responses/dividends-response.interface.ts | 2 +- .../historical-response.interface.ts | 2 +- .../responses/import-response.interface.ts | 2 +- ...rket-data-of-markets-response.interface.ts | 2 +- .../portfolio-holding-response.interface.ts | 2 +- .../responses/quotes-response.interface.ts | 2 +- .../lib}/interfaces/symbol-item.interface.ts | 0 .../account-balances.component.ts | 1 + .../accounts-table.component.ts | 1 + .../activities-filter.component.ts | 1 + .../activities-table.component.stories.ts | 2 +- .../activities-table.component.ts | 7 +++++-- .../assistant-list-item.component.ts | 1 + .../src/lib/assistant/assistant.component.ts | 1 + .../benchmark-detail-dialog.component.ts | 1 + .../src/lib/benchmark/benchmark.component.ts | 1 + ...cal-market-data-editor-dialog.component.ts | 1 + ...historical-market-data-editor.component.ts | 1 + .../portfolio-filter-form.component.ts | 1 + .../symbol-autocomplete.component.ts | 1 + .../top-holdings/top-holdings.component.ts | 1 + 89 files changed, 159 insertions(+), 134 deletions(-) rename {apps/api/src/app/order => libs/common/src/lib}/interfaces/activities.interface.ts (100%) create mode 100644 libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts rename {apps/api/src/app/symbol => libs/common/src/lib}/interfaces/symbol-item.interface.ts (100%) diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts index 1094858cb..d088bf3ac 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -8,7 +8,6 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { @@ -18,6 +17,7 @@ import { import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config'; import { DataProviderGhostfolioAssetProfileResponse, + DataProviderHistoricalResponse, DataProviderInfo, DividendsResponse, HistoricalResponse, diff --git a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts b/apps/api/src/app/exchange-rate/exchange-rate.controller.ts index fc9e61d61..239b4b27a 100644 --- a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts +++ b/apps/api/src/app/exchange-rate/exchange-rate.controller.ts @@ -1,5 +1,5 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/common/interfaces'; import { Controller, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 669432db5..cac466192 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -1,10 +1,6 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { - Activity, - ActivityError -} from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -19,7 +15,11 @@ import { getAssetProfileIdentifier, parseDate } from '@ghostfolio/common/helper'; -import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; +import { + Activity, + ActivityError, + AssetProfileIdentifier +} from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { AccountWithPlatform, diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts index 24fe2b2f3..7b5ab1a0d 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts @@ -1,10 +1,13 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; -import { Filter, HistoricalDataItem } from '@ghostfolio/common/interfaces'; +import { + Activity, + Filter, + HistoricalDataItem +} from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Injectable } from '@nestjs/common'; diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 10e5c15cb..b3cedb00b 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface'; @@ -26,6 +25,7 @@ import { resetHours } from '@ghostfolio/common/helper'; import { + Activity, AssetProfileIdentifier, DataProviderInfo, Filter, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index aa174f319..f0e2f6488 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 69b6c3dfc..10b1fabd3 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index a3cb8716e..32cd9f7d4 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index ae083a7db..84cab99e1 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts index 87893e647..1f64684a0 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, loadExportFile, @@ -16,7 +15,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { ExportResponse } from '@ghostfolio/common/interfaces'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index cef8938c2..ce639b564 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, loadExportFile, @@ -15,7 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { ExportResponse } from '@ghostfolio/common/interfaces'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index 36e6fa900..0c111fab2 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -15,6 +14,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts index 5a4dfdc07..618dc805c 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, loadExportFile, @@ -15,7 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { ExportResponse } from '@ghostfolio/common/interfaces'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index 2ee367530..a7cbe746c 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, loadExportFile, @@ -15,7 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { ExportResponse } from '@ghostfolio/common/interfaces'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts index 002be9154..aae77c876 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index bf0b15020..495728e22 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -15,6 +14,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts index 32822014c..1fd88dacc 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts index 08015da5b..4c8ccdcf5 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index e5b128085..0331e163e 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index cf330d136..650944421 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, loadExportFile, @@ -15,7 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { ExportResponse } from '@ghostfolio/common/interfaces'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index 681169062..2e408dc3c 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, loadExportFile, @@ -15,7 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { ExportResponse } from '@ghostfolio/common/interfaces'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index fc1d477a6..3c7c3be4b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -1,4 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, symbolProfileDummyData, @@ -14,6 +13,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; 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 1c53430f6..9362184c7 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts @@ -1,4 +1,4 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { Activity } from '@ghostfolio/common/interfaces'; export interface PortfolioOrder extends Pick { date: string; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1ae6190e1..084c8f4ed 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1,7 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; @@ -40,6 +39,7 @@ import { import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper'; import { AccountsResponse, + Activity, EnhancedSymbolProfile, Filter, HistoricalDataItem, diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index b374a914b..501692ae5 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -1,8 +1,11 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { LookupResponse } from '@ghostfolio/common/interfaces'; +import { + DataProviderHistoricalResponse, + LookupResponse, + SymbolItem +} from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -22,7 +25,6 @@ import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { isDate, isEmpty } from 'lodash'; -import { SymbolItem } from './interfaces/symbol-item.interface'; import { SymbolService } from './symbol.service'; @Controller('symbol') diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 9eac234c9..15498e80d 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -1,21 +1,18 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; -import { - DataGatheringItem, - DataProviderHistoricalResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, HistoricalDataItem, - LookupResponse + LookupResponse, + SymbolItem } from '@ghostfolio/common/interfaces'; import { UserWithSettings } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { format, subDays } from 'date-fns'; -import { SymbolItem } from './interfaces/symbol-item.interface'; - @Injectable() export class SymbolService { public constructor( diff --git a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts index 1e631f8c8..3cf935b1e 100644 --- a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts +++ b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts @@ -7,14 +7,12 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index e06cb6ab3..4123cc6cc 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -7,14 +7,12 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupItem, LookupResponse } from '@ghostfolio/common/interfaces'; 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 53ef5c5e4..5a088c0e4 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -1,10 +1,6 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; @@ -23,6 +19,8 @@ import { } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, + DataProviderHistoricalResponse, + DataProviderResponse, LookupItem, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index b837b2e6f..b93ca492a 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -7,10 +7,6 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { DEFAULT_CURRENCY, @@ -18,7 +14,9 @@ import { } from '@ghostfolio/common/config'; import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupItem, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 0caad99ca..90035b1a8 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -8,10 +8,6 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { DEFAULT_CURRENCY, @@ -19,7 +15,9 @@ import { } from '@ghostfolio/common/config'; import { DATE_FORMAT, isCurrency, parseDate } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupItem, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts index 9928af8eb..afbecc118 100644 --- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -8,10 +8,6 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { HEADER_KEY_TOKEN, @@ -20,7 +16,9 @@ import { import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DataProviderGhostfolioAssetProfileResponse, + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, DividendsResponse, HistoricalResponse, LookupResponse, diff --git a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts index fc188c345..ba1e5bbe5 100644 --- a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts +++ b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts @@ -7,15 +7,13 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index 38eb62a2b..a55c9f328 100644 --- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -1,9 +1,7 @@ import { DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; -import { DataProviderInfo, + DataProviderResponse, LookupResponse } from '@ghostfolio/common/interfaces'; import { Granularity } from '@ghostfolio/common/types'; diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 00c28d9d2..f18da49ab 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -7,10 +7,6 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { @@ -19,7 +15,9 @@ import { getYesterday } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupResponse, ScraperConfiguration } from '@ghostfolio/common/interfaces'; 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 4d22e0feb..d6bc8d0e4 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 @@ -7,17 +7,15 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { ghostfolioFearAndGreedIndexSymbol, ghostfolioFearAndGreedIndexSymbolStocks } from '@ghostfolio/common/config'; import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index b36b0f215..de8807098 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -9,14 +9,12 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { - DataProviderHistoricalResponse, - DataProviderResponse -} from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, LookupItem, LookupResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/services/interfaces/interfaces.ts b/apps/api/src/services/interfaces/interfaces.ts index 492c2bd35..87eaa3a75 100644 --- a/apps/api/src/services/interfaces/interfaces.ts +++ b/apps/api/src/services/interfaces/interfaces.ts @@ -1,22 +1,4 @@ -import { - AssetProfileIdentifier, - DataProviderInfo -} from '@ghostfolio/common/interfaces'; -import { MarketState } from '@ghostfolio/common/types'; - -import { DataSource } from '@prisma/client'; - -export interface DataProviderHistoricalResponse { - marketPrice: number; -} - -export interface DataProviderResponse { - currency: string; - dataProviderInfo?: DataProviderInfo; - dataSource: DataSource; - marketPrice: number; - marketState: MarketState; -} +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; export interface DataGatheringItem extends AssetProfileIdentifier { date?: Date; diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index 94cb22699..47ba48f4e 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -1,5 +1,5 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto'; -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component'; @@ -9,6 +9,7 @@ import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/conf import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { AccountBalancesResponse, + Activity, HistoricalDataItem, PortfolioPosition, User diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index a56f6dec5..83b2586ce 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 6642d2315..76d00bb10 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; diff --git a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts index 48a6ca432..0d9e6f8bd 100644 --- a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts +++ b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index 88e8faa9d..4fd34acc0 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts index 336fb9b22..2d1babeb4 100644 --- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts +++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 3f011fec4..24fa82d02 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces'; import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component'; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index b443a37e7..a6c02f7dc 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,5 +1,5 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { DataService } from '@ghostfolio/client/services/data.service'; @@ -11,6 +11,7 @@ import { } from '@ghostfolio/common/config'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { + Activity, DataProviderInfo, EnhancedSymbolProfile, Filter, diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index 5ed39d5be..9b40f8f50 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { diff --git a/apps/client/src/app/components/rules/rules.component.ts b/apps/client/src/app/components/rules/rules.component.ts index 80a59740b..7dd322c21 100644 --- a/apps/client/src/app/components/rules/rules.component.ts +++ b/apps/client/src/app/components/rules/rules.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { GfRuleComponent } from '@ghostfolio/client/components/rule/rule.component'; import { diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 315f86244..9c21c4f34 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; diff --git a/apps/client/src/app/components/user-account-access/user-account-access.component.ts b/apps/client/src/app/components/user-account-access/user-account-access.component.ts index afcb9d9c8..de2483e50 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 3a1616b6f..010d727c6 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto'; import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index beb815e0c..8df990d3d 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; diff --git a/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts b/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts index 368c7f2f0..4af1dbe6f 100644 --- a/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 6ee02bd8e..d6a1540d0 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -1,5 +1,5 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; import { IcsService } from '@ghostfolio/client/services/ics/ics.service'; @@ -7,7 +7,11 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { downloadAsFile } from '@ghostfolio/common/helper'; -import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces'; +import { + Activity, + AssetProfileIdentifier, + User +} from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; 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 3261e9752..4d2f958e7 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 @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UserService } from '@ghostfolio/client/services/user/user.service'; diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts index cc454a66a..5206aacf9 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts @@ -1,5 +1,4 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; -import { User } from '@ghostfolio/common/interfaces'; +import { Activity, User } from '@ghostfolio/common/interfaces'; import { Account } from '@prisma/client'; 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 0c0054e9b..e2b1403c0 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 @@ -1,14 +1,14 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; -import { PortfolioPosition } from '@ghostfolio/common/interfaces'; +import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts index 364564383..bbd50a0e1 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { GfRulesComponent } from '@ghostfolio/client/components/rules/rules.component'; import { DataService } from '@ghostfolio/client/services/data.service'; diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index cdac3ed38..68a02facc 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -1,7 +1,7 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto'; -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { HEADER_KEY_SKIP_INTERCEPTOR, HEADER_KEY_TOKEN @@ -15,6 +15,7 @@ import { AdminUsersResponse, AssetProfileIdentifier, DataProviderGhostfolioStatusResponse, + DataProviderHistoricalResponse, EnhancedSymbolProfile, Filter } from '@ghostfolio/common/interfaces'; diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index f83746009..60118d205 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto'; import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto'; @@ -10,12 +11,10 @@ import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto' import { CreateWatchlistItemDto } from '@ghostfolio/api/app/endpoints/watchlist/create-watchlist-item.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; -import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto'; import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface'; import { UpdateOwnAccessTokenDto } from '@ghostfolio/api/app/user/update-own-access-token.dto'; import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { PropertyDto } from '@ghostfolio/api/services/property/property.dto'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { @@ -34,6 +33,7 @@ import { BenchmarkResponse, CreateStripeCheckoutSessionResponse, DataProviderHealthResponse, + DataProviderHistoricalResponse, ExportResponse, Filter, ImportResponse, @@ -50,6 +50,7 @@ import { PortfolioPerformanceResponse, PortfolioReportResponse, PublicPortfolioResponse, + SymbolItem, User, WatchlistResponse } from '@ghostfolio/common/interfaces'; diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 0f2715e47..607b8a0a0 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -1,9 +1,10 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { parseDate as parseDateHelper } from '@ghostfolio/common/helper'; +import { Activity } from '@ghostfolio/common/interfaces'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; diff --git a/apps/client/src/app/services/web-authn.service.ts b/apps/client/src/app/services/web-authn.service.ts index 3885b2f94..9ace943b6 100644 --- a/apps/client/src/app/services/web-authn.service.ts +++ b/apps/client/src/app/services/web-authn.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto'; import { PublicKeyCredentialCreationOptionsJSON, diff --git a/eslint.config.cjs b/eslint.config.cjs index a88d0cc85..5962e261d 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -18,16 +18,19 @@ module.exports = [ files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], rules: { '@nx/enforce-module-boundaries': [ - 'warn', + 'error', { - enforceBuildableLibDependency: true, allow: [], + allowCircularSelfDependency: true, depConstraints: [ { sourceTag: '*', onlyDependOnLibsWithTags: ['*'] } - ] + ], + enforceBuildableLibDependency: true, + // Temporary fix, should be removed eventually + ignoredCircularDependencies: [['client', 'ui']] } ], '@typescript-eslint/no-extra-semi': 'error', diff --git a/apps/api/src/app/order/interfaces/activities.interface.ts b/libs/common/src/lib/interfaces/activities.interface.ts similarity index 100% rename from apps/api/src/app/order/interfaces/activities.interface.ts rename to libs/common/src/lib/interfaces/activities.interface.ts diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 5c516a4a6..c47af2d97 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -1,5 +1,6 @@ import type { Access } from './access.interface'; import type { AccountBalance } from './account-balance.interface'; +import type { Activity, ActivityError } from './activities.interface'; import type { AdminData } from './admin-data.interface'; import type { AdminJobs } from './admin-jobs.interface'; import type { AdminMarketDataDetails } from './admin-market-data-details.interface'; @@ -52,6 +53,10 @@ import type { DataEnhancerHealthResponse } from './responses/data-enhancer-healt import type { DataProviderGhostfolioAssetProfileResponse } from './responses/data-provider-ghostfolio-asset-profile-response.interface'; import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface'; import type { DataProviderHealthResponse } from './responses/data-provider-health-response.interface'; +import type { + DataProviderResponse, + DataProviderHistoricalResponse +} from './responses/data-provider-response.interface'; import type { DividendsResponse } from './responses/dividends-response.interface'; import type { ResponseError } from './responses/errors.interface'; import type { ExportResponse } from './responses/export-response.interface'; @@ -74,6 +79,7 @@ import type { WatchlistResponse } from './responses/watchlist-response.interface import type { ScraperConfiguration } from './scraper-configuration.interface'; import type { Statistics } from './statistics.interface'; import type { SubscriptionOffer } from './subscription-offer.interface'; +import type { SymbolItem } from './symbol-item.interface'; import type { SymbolMetrics } from './symbol-metrics.interface'; import type { SystemMessage } from './system-message.interface'; import type { TabConfiguration } from './tab-configuration.interface'; @@ -90,6 +96,8 @@ export { AccountResponse, AccountsResponse, ActivitiesResponse, + Activity, + ActivityError, ActivityResponse, AdminData, AdminJobs, @@ -114,7 +122,9 @@ export { DataProviderGhostfolioAssetProfileResponse, DataProviderGhostfolioStatusResponse, DataProviderHealthResponse, + DataProviderHistoricalResponse, DataProviderInfo, + DataProviderResponse, DividendsResponse, EnhancedSymbolProfile, ExportResponse, @@ -156,8 +166,9 @@ export { ScraperConfiguration, Statistics, SubscriptionOffer, - SystemMessage, + SymbolItem, SymbolMetrics, + SystemMessage, TabConfiguration, ToggleOption, User, diff --git a/libs/common/src/lib/interfaces/responses/activities-response.interface.ts b/libs/common/src/lib/interfaces/responses/activities-response.interface.ts index e6abe4618..863ae4665 100644 --- a/libs/common/src/lib/interfaces/responses/activities-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/activities-response.interface.ts @@ -1,4 +1,4 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { Activity } from '@ghostfolio/common/interfaces'; export interface ActivitiesResponse { activities: Activity[]; diff --git a/libs/common/src/lib/interfaces/responses/activity-response.interface.ts b/libs/common/src/lib/interfaces/responses/activity-response.interface.ts index 5dd338627..d26f13a5a 100644 --- a/libs/common/src/lib/interfaces/responses/activity-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/activity-response.interface.ts @@ -1,3 +1,3 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { Activity } from '@ghostfolio/common/interfaces'; export interface ActivityResponse extends Activity {} diff --git a/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts b/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts new file mode 100644 index 000000000..ff152b1b2 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/data-provider-response.interface.ts @@ -0,0 +1,16 @@ +import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { MarketState } from '@ghostfolio/common/types'; + +import { DataSource } from '@prisma/client'; + +export interface DataProviderHistoricalResponse { + marketPrice: number; +} + +export interface DataProviderResponse { + currency: string; + dataProviderInfo?: DataProviderInfo; + dataSource: DataSource; + marketPrice: number; + marketState: MarketState; +} diff --git a/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts b/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts index 15afc54c9..8bbd8b755 100644 --- a/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts @@ -1,4 +1,4 @@ -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/common/interfaces'; export interface DividendsResponse { dividends: { diff --git a/libs/common/src/lib/interfaces/responses/historical-response.interface.ts b/libs/common/src/lib/interfaces/responses/historical-response.interface.ts index 24383ab07..211b19b4d 100644 --- a/libs/common/src/lib/interfaces/responses/historical-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/historical-response.interface.ts @@ -1,4 +1,4 @@ -import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/common/interfaces'; export interface HistoricalResponse { historicalData: { diff --git a/libs/common/src/lib/interfaces/responses/import-response.interface.ts b/libs/common/src/lib/interfaces/responses/import-response.interface.ts index be2da9837..24b0e4f4b 100644 --- a/libs/common/src/lib/interfaces/responses/import-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/import-response.interface.ts @@ -1,4 +1,4 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { Activity } from '@ghostfolio/common/interfaces'; export interface ImportResponse { activities: Activity[]; diff --git a/libs/common/src/lib/interfaces/responses/market-data-of-markets-response.interface.ts b/libs/common/src/lib/interfaces/responses/market-data-of-markets-response.interface.ts index aecfbb28b..997a42737 100644 --- a/libs/common/src/lib/interfaces/responses/market-data-of-markets-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/market-data-of-markets-response.interface.ts @@ -1,4 +1,4 @@ -import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; +import { SymbolItem } from '@ghostfolio/common/interfaces'; export interface MarketDataOfMarketsResponse { fearAndGreedIndex: { diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts index b82a8f85d..31f027ee9 100644 --- a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts @@ -1,5 +1,5 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { + Activity, Benchmark, DataProviderInfo, EnhancedSymbolProfile, diff --git a/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts b/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts index 8b9b09cb8..933220ed7 100644 --- a/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts @@ -1,4 +1,4 @@ -import { DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderResponse } from '@ghostfolio/common/interfaces'; export interface QuotesResponse { quotes: { [symbol: string]: DataProviderResponse }; diff --git a/apps/api/src/app/symbol/interfaces/symbol-item.interface.ts b/libs/common/src/lib/interfaces/symbol-item.interface.ts similarity index 100% rename from apps/api/src/app/symbol/interfaces/symbol-item.interface.ts rename to libs/common/src/lib/interfaces/symbol-item.interface.ts diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index caeaebc64..904e7d46c 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index 607fa67dc..b96905981 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { getLocale } from '@ghostfolio/common/helper'; diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts index cb659988a..177312490 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { Filter, FilterGroup } from '@ghostfolio/common/interfaces'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts index 5e774730b..78e712c89 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts @@ -1,5 +1,5 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; +import { Activity } from '@ghostfolio/common/interfaces'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index 1313ef1e2..99ba2aded 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -1,4 +1,4 @@ -import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +/* eslint-disable @nx/enforce-module-boundaries */ import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; @@ -7,7 +7,10 @@ import { TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config'; import { getLocale } from '@ghostfolio/common/helper'; -import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; +import { + Activity, + AssetProfileIdentifier +} from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { SelectionModel } from '@angular/cdk/collections'; diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts index f9034df71..059bbaf9e 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index eaf96f496..e9c6e77b3 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts index 8f7d30847..bcac9c6b5 100644 --- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { DataService } from '@ghostfolio/client/services/data.service'; diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index bb66acba8..4c1ca97cd 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper'; diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts index d2d53f7ca..21202981d 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts index 002422c57..b36a70e69 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; import { diff --git a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts index 794f43d4d..274c3f994 100644 --- a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts +++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces'; diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index 80315fc06..dcfcaf3f1 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { DataService } from '@ghostfolio/client/services/data.service'; import { LookupItem } from '@ghostfolio/common/interfaces'; diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts index c9f7e0372..b67cc1b80 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts @@ -1,3 +1,4 @@ +/* eslint-disable @nx/enforce-module-boundaries */ import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { getLocale } from '@ghostfolio/common/helper'; import { From 66a3e319a878ad9fb1cd054b1f25cd4d00e7ea47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Fri, 14 Nov 2025 20:02:03 +0100 Subject: [PATCH 018/157] Feature/separate Google OAuth and token authentication (#5915) * Separate Google OAuth and token authentication * Update changelog --- CHANGELOG.md | 5 ++ apps/api/src/app/info/info.service.ts | 12 ++-- .../configuration/configuration.service.ts | 3 +- .../interfaces/environment.interface.ts | 3 +- .../app/components/header/header.component.ts | 15 +++-- .../interfaces/interfaces.ts | 3 +- .../login-with-access-token-dialog.html | 67 +++++++++++-------- .../pages/features/features-page.component.ts | 6 ++ .../src/app/pages/features/features-page.html | 2 +- .../pages/landing/landing-page.component.ts | 1 + .../pages/pricing/pricing-page.component.ts | 12 +++- .../src/app/pages/pricing/pricing-page.html | 2 +- .../pages/register/register-page.component.ts | 12 +++- .../src/app/pages/register/register-page.html | 22 +++--- libs/common/src/lib/permissions.ts | 5 +- 15 files changed, 112 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4437d077c..5e9e362cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored the get holding functionality in the portfolio service - Changed the user data loading in the user detail dialog of the admin control panel’s users section to fetch data on demand +- Exposed the authentication with access token as an environment variable (`ENABLE_FEATURE_AUTH_TOKEN`) - Improved the language localization for German (`de`) - Upgraded `prisma` from version `6.18.0` to `6.19.0` +### Todo + +- Rename the environment variable from `ENABLE_FEATURE_SOCIAL_LOGIN` to `ENABLE_FEATURE_AUTH_GOOGLE` + ## 2.216.0 - 2025-11-10 ### Changed diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index c31f601e3..634fc959c 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -51,6 +51,14 @@ export class InfoService { const globalPermissions: string[] = []; + if (this.configurationService.get('ENABLE_FEATURE_AUTH_GOOGLE')) { + globalPermissions.push(permissions.enableAuthGoogle); + } + + if (this.configurationService.get('ENABLE_FEATURE_AUTH_TOKEN')) { + globalPermissions.push(permissions.enableAuthToken); + } + if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { info.fearAndGreedDataSource = encodeDataSource( @@ -70,10 +78,6 @@ export class InfoService { ); } - if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) { - globalPermissions.push(permissions.enableSocialLogin); - } - if (this.configurationService.get('ENABLE_FEATURE_STATISTICS')) { globalPermissions.push(permissions.enableStatistics); } diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index 473d909ee..cb9fde832 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -40,9 +40,10 @@ export class ConfigurationService { DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: json({ default: [] }), + ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }), + ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }), - ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }), ENABLE_FEATURE_STATISTICS: bool({ default: false }), ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }), ENABLE_FEATURE_SYSTEM_MESSAGE: bool({ default: false }), diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 2f94739fb..f2ee84926 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -16,9 +16,10 @@ export interface Environment extends CleanedEnvAccessors { DATA_SOURCE_IMPORT: string; DATA_SOURCES: string[]; DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: string[]; + ENABLE_FEATURE_AUTH_GOOGLE: boolean; + ENABLE_FEATURE_AUTH_TOKEN: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean; - ENABLE_FEATURE_SOCIAL_LOGIN: boolean; ENABLE_FEATURE_STATISTICS: boolean; ENABLE_FEATURE_SUBSCRIPTION: boolean; ENABLE_FEATURE_SYSTEM_MESSAGE: boolean; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 24fa82d02..03d53e058 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -105,7 +105,8 @@ export class GfHeaderComponent implements OnChanges { public hasFilters: boolean; public hasImpersonationId: boolean; - public hasPermissionForSocialLogin: boolean; + public hasPermissionForAuthGoogle: boolean; + public hasPermissionForAuthToken: boolean; public hasPermissionForSubscription: boolean; public hasPermissionToAccessAdminControl: boolean; public hasPermissionToAccessAssistant: boolean; @@ -165,9 +166,14 @@ export class GfHeaderComponent implements OnChanges { public ngOnChanges() { this.hasFilters = this.userService.hasFilters(); - this.hasPermissionForSocialLogin = hasPermission( + this.hasPermissionForAuthGoogle = hasPermission( this.info?.globalPermissions, - permissions.enableSocialLogin + permissions.enableAuthGoogle + ); + + this.hasPermissionForAuthToken = hasPermission( + this.info?.globalPermissions, + permissions.enableAuthToken ); this.hasPermissionForSubscription = hasPermission( @@ -280,7 +286,8 @@ export class GfHeaderComponent implements OnChanges { autoFocus: false, data: { accessToken: '', - hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin, + hasPermissionToUseAuthGoogle: this.hasPermissionForAuthGoogle, + hasPermissionToUseAuthToken: this.hasPermissionForAuthToken, title: $localize`Sign in` }, width: '30rem' diff --git a/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts index 2fa8b7ea4..c7c4ab3fd 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts @@ -1,5 +1,6 @@ export interface LoginWithAccessTokenDialogParams { accessToken: string; - hasPermissionToUseSocialLogin: boolean; + hasPermissionToUseAuthGoogle: boolean; + hasPermissionToUseAuthToken: boolean; title: string; } diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html index 15e68822a..bc232cfb7 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -3,28 +3,35 @@
- - Security Token - - - + + + } - @if (data.hasPermissionToUseSocialLogin) { + @if ( + data.hasPermissionToUseAuthGoogle && data.hasPermissionToUseAuthToken + ) {
or
+ } + + @if (data.hasPermissionToUseAuthGoogle) {
- + @if (data.hasPermissionToUseAuthToken) { + + }
diff --git a/apps/client/src/app/pages/features/features-page.component.ts b/apps/client/src/app/pages/features/features-page.component.ts index dc9d30f07..dc2dfaf42 100644 --- a/apps/client/src/app/pages/features/features-page.component.ts +++ b/apps/client/src/app/pages/features/features-page.component.ts @@ -25,6 +25,7 @@ import { Subject, takeUntil } from 'rxjs'; }) export class GfFeaturesPageComponent implements OnDestroy { public hasPermissionForSubscription: boolean; + public hasPermissionToCreateUser: boolean; public info: InfoItem; public routerLinkRegister = publicRoutes.register.routerLink; public routerLinkResources = publicRoutes.resources.routerLink; @@ -55,6 +56,11 @@ export class GfFeaturesPageComponent implements OnDestroy { this.info?.globalPermissions, permissions.enableSubscription ); + + this.hasPermissionToCreateUser = hasPermission( + this.info?.globalPermissions, + permissions.createUserAccount + ); } public ngOnDestroy() { diff --git a/apps/client/src/app/pages/features/features-page.html b/apps/client/src/app/pages/features/features-page.html index 7d8f3eda0..d172347f7 100644 --- a/apps/client/src/app/pages/features/features-page.html +++ b/apps/client/src/app/pages/features/features-page.html @@ -309,7 +309,7 @@
- @if (!user) { + @if (hasPermissionToCreateUser && !user) { - } @else if (!user) { + } @else if (hasPermissionToCreateUser && !user) {
- - @if (hasPermissionForSocialLogin) { + @if (hasPermissionForAuthToken) { + + } + @if (hasPermissionForAuthToken && hasPermissionForAuthGoogle) {
or
+ } + @if (hasPermissionForAuthGoogle) {
{ return ( - permission !== permissions.enableSocialLogin && + permission !== permissions.enableAuthGoogle && permission !== permissions.enableSubscription ); }); From 9d25d5c5f490c8534ec8759c962ecd315af329b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Sat, 15 Nov 2025 10:51:48 +0100 Subject: [PATCH 019/157] Feature/automatically gather required exchange rates (#5917) * Automatically gather required exchange rates * Update changelog --- CHANGELOG.md | 4 ++ apps/api/src/app/order/order.service.ts | 10 +++ .../src/events/asset-profile-changed.event.ts | 11 ++++ .../events/asset-profile-changed.listener.ts | 61 +++++++++++++++++++ apps/api/src/events/events.module.ts | 17 +++++- .../configuration/configuration.service.ts | 1 + .../interfaces/environment.interface.ts | 1 + 7 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 apps/api/src/events/asset-profile-changed.event.ts create mode 100644 apps/api/src/events/asset-profile-changed.listener.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e9e362cd..51ae02bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Introduced support for automatically gathering required exchange rates, exposed as an environment variable (`ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES`) + ### Changed - Refactored the get holding functionality in the portfolio service diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index e4c642977..7dc6c646d 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -1,4 +1,5 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; +import { AssetProfileChangedEvent } from '@ghostfolio/api/events/asset-profile-changed.event'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; @@ -225,6 +226,15 @@ export class OrderService { }); } + this.eventEmitter.emit( + AssetProfileChangedEvent.getName(), + new AssetProfileChangedEvent({ + currency: order.SymbolProfile.currency, + dataSource: order.SymbolProfile.dataSource, + symbol: order.SymbolProfile.symbol + }) + ); + this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ diff --git a/apps/api/src/events/asset-profile-changed.event.ts b/apps/api/src/events/asset-profile-changed.event.ts new file mode 100644 index 000000000..46a8c5db4 --- /dev/null +++ b/apps/api/src/events/asset-profile-changed.event.ts @@ -0,0 +1,11 @@ +import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; + +export class AssetProfileChangedEvent { + public constructor( + public readonly data: AssetProfileIdentifier & { currency: string } + ) {} + + public static getName(): string { + return 'assetProfile.changed'; + } +} diff --git a/apps/api/src/events/asset-profile-changed.listener.ts b/apps/api/src/events/asset-profile-changed.listener.ts new file mode 100644 index 000000000..ad80ee4a5 --- /dev/null +++ b/apps/api/src/events/asset-profile-changed.listener.ts @@ -0,0 +1,61 @@ +import { OrderService } from '@ghostfolio/api/app/order/order.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 { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; +import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; + +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { AssetProfileChangedEvent } from './asset-profile-changed.event'; + +@Injectable() +export class AssetProfileChangedListener { + public constructor( + private readonly configurationService: ConfigurationService, + private readonly dataGatheringService: DataGatheringService, + private readonly dataProviderService: DataProviderService, + private readonly exchangeRateDataService: ExchangeRateDataService, + private readonly orderService: OrderService + ) {} + + @OnEvent(AssetProfileChangedEvent.getName()) + public async handleAssetProfileChanged(event: AssetProfileChangedEvent) { + Logger.log( + `Asset profile of ${event.data.symbol} (${event.data.dataSource}) has changed`, + 'AssetProfileChangedListener' + ); + + if ( + this.configurationService.get( + 'ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES' + ) === false || + event.data.currency === DEFAULT_CURRENCY + ) { + return; + } + + const existingCurrencies = this.exchangeRateDataService.getCurrencies(); + + if (!existingCurrencies.includes(event.data.currency)) { + Logger.log( + `New currency ${event.data.currency} has been detected`, + 'AssetProfileChangedListener' + ); + + await this.exchangeRateDataService.initialize(); + } + + const { dateOfFirstActivity } = + await this.orderService.getStatisticsByCurrency(event.data.currency); + + if (dateOfFirstActivity) { + await this.dataGatheringService.gatherSymbol({ + dataSource: this.dataProviderService.getDataSourceForExchangeRates(), + date: dateOfFirstActivity, + symbol: `${DEFAULT_CURRENCY}${event.data.currency}` + }); + } + } +} diff --git a/apps/api/src/events/events.module.ts b/apps/api/src/events/events.module.ts index 0e6b25ba4..ece67ebe0 100644 --- a/apps/api/src/events/events.module.ts +++ b/apps/api/src/events/events.module.ts @@ -1,11 +1,24 @@ +import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; +import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.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'; +import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { Module } from '@nestjs/common'; +import { AssetProfileChangedListener } from './asset-profile-changed.listener'; import { PortfolioChangedListener } from './portfolio-changed.listener'; @Module({ - imports: [RedisCacheModule], - providers: [PortfolioChangedListener] + imports: [ + ConfigurationModule, + DataGatheringModule, + DataProviderModule, + ExchangeRateDataModule, + OrderModule, + RedisCacheModule + ], + providers: [AssetProfileChangedListener, PortfolioChangedListener] }) export class EventsModule {} diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index cb9fde832..f37189569 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -43,6 +43,7 @@ export class ConfigurationService { ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }), ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), + ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: bool({ default: true }), ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }), ENABLE_FEATURE_STATISTICS: bool({ default: false }), ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }), diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index f2ee84926..3a2ac687c 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -19,6 +19,7 @@ export interface Environment extends CleanedEnvAccessors { ENABLE_FEATURE_AUTH_GOOGLE: boolean; ENABLE_FEATURE_AUTH_TOKEN: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; + ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean; ENABLE_FEATURE_STATISTICS: boolean; ENABLE_FEATURE_SUBSCRIPTION: boolean; From 6deaccfe16cd3770aaaf9334a5a23b3b17cde5e4 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:59:36 +0700 Subject: [PATCH 020/157] Task/enforce module boundaries for client module (#5944) * feat(lib): move SymbolPipe to common lib * feat(lib): move CreateAccountBalanceDto to common lib * feat(lib): move IsCurrencyCode validator to common lib * feat(lib): move UpdateAssetProfileDto to common lib * feat(lib): move UpdateUserSettingDto to common lib * feat(lib): move CreateAccessDto to common lib * feat(lib): move UpdateAccessDto to common lib * feat(lib): move CreateTagDto to common lib * feat(lib): move UpdateTagDto to common lib * feat(lib): move CreatePlatformDto to common lib * feat(lib): move UpdatePlatformDto to common lib * feat(lib): move CreateOrderDto to common lib * feat(lib): move UpdateOrderDto to common lib * feat(lib): move RuleSettings interface to common lib * feat(lib): move CreateAccountDto TransferBalanceDto UpdateAccountDto to common lib * feat(lib): move CreateAccountWithBalancesDto to common lib * feat(lib): move CreateAssetProfileDto and CreateAssetProfileWithMarketDataDto to common lib * feat(lib): move AuthDeviceDto to common lib * feat(lib): move simplewebauthn interfaces to common lib This includes AssertionCredentialJSON, AttestationCredentialJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON. * feat(lib): move UpdateMarketDataDto to common lib * feat(lib): move UpdateBulkMarketDataDto to common lib * feat(lib): move CreateWatchlistItemDto to common lib * feat(lib): move DeleteOwnUserDto to common lib * feat(lib): move UserItem interface to common lib * feat(lib): move UpdateOwnAccessTokenDto to common lib * feat(lib): move PropertyDto to common lib --- apps/api/src/app/access/access.controller.ts | 3 +- .../account-balance.controller.ts | 2 +- .../account-balance.service.ts | 3 +- .../api/src/app/account/account.controller.ts | 8 +-- apps/api/src/app/admin/admin.controller.ts | 8 +-- apps/api/src/app/auth/auth.controller.ts | 10 ++-- .../api/src/app/auth/interfaces/interfaces.ts | 2 +- apps/api/src/app/auth/web-auth.service.ts | 11 ++-- .../market-data/market-data.controller.ts | 3 +- .../update-bulk-market-data.dto.ts | 24 --------- .../src/app/endpoints/tags/tags.controller.ts | 4 +- .../watchlist/watchlist.controller.ts | 2 +- apps/api/src/app/import/import-data.dto.ts | 11 ++-- apps/api/src/app/import/import.service.ts | 8 +-- apps/api/src/app/order/order.controller.ts | 3 +- .../src/app/platform/platform.controller.ts | 3 +- apps/api/src/app/portfolio/rules.service.ts | 2 +- apps/api/src/app/user/user.controller.ts | 10 ++-- .../src/models/interfaces/rule.interface.ts | 3 +- apps/api/src/models/rule.ts | 2 +- .../current-investment.ts | 7 ++- .../account-cluster-risk/single-account.ts | 7 ++- .../rules/asset-class-cluster-risk/equity.ts | 7 ++- .../asset-class-cluster-risk/fixed-income.ts | 7 ++- .../base-currency-current-investment.ts | 7 ++- .../current-investment.ts | 7 ++- .../developed-markets.ts | 3 +- .../emerging-markets.ts | 3 +- .../emergency-fund/emergency-fund-setup.ts | 3 +- .../fees/fee-ratio-initial-investment.ts | 3 +- .../models/rules/liquidity/buying-power.ts | 3 +- .../interfaces/rule-settings.interface.ts | 2 +- .../market-data/market-data.service.ts | 2 +- .../account-detail-dialog.component.ts | 3 +- .../admin-market-data.component.ts | 2 +- .../asset-profile-dialog.component.ts | 3 +- .../admin-platform.component.ts | 4 +- ...ate-or-update-platform-dialog.component.ts | 4 +- .../admin-tag/admin-tag.component.ts | 4 +- .../create-or-update-tag-dialog.component.ts | 4 +- .../app/components/header/header.component.ts | 3 +- .../holding-detail-dialog.component.ts | 3 +- .../src/app/components/rule/rule.component.ts | 5 +- .../app/components/rules/rules.component.ts | 3 +- ...reate-or-update-access-dialog.component.ts | 4 +- .../user-account-access.component.ts | 3 +- .../pages/accounts/accounts-page.component.ts | 9 ++-- ...eate-or-update-account-dialog.component.ts | 4 +- .../transfer-balance-dialog.component.ts | 3 +- .../activities/activities-page.component.ts | 4 +- ...ate-or-update-activity-dialog.component.ts | 4 +- .../import-activities-dialog.component.ts | 11 ++-- .../portfolio/x-ray/x-ray-page.component.ts | 3 +- apps/client/src/app/services/admin.service.ts | 11 ++-- apps/client/src/app/services/data.service.ts | 39 +++++++------- .../app/services/import-activities.service.ts | 11 ++-- .../src/app/services/web-authn.service.ts | 7 ++- .../common/src/lib/dtos}/auth-device.dto.ts | 0 .../common/src/lib/dtos}/create-access.dto.ts | 0 .../lib/dtos}/create-account-balance.dto.ts | 0 .../dtos}/create-account-with-balances.dto.ts | 2 +- .../src/lib/dtos}/create-account.dto.ts | 2 +- ...eate-asset-profile-with-market-data.dto.ts | 2 +- .../src/lib/dtos}/create-asset-profile.dto.ts | 2 +- .../common/src/lib/dtos}/create-order.dto.ts | 2 +- .../src/lib/dtos}/create-platform.dto.ts | 0 .../common/src/lib/dtos}/create-tag.dto.ts | 0 .../lib/dtos}/create-watchlist-item.dto.ts | 0 .../src/lib/dtos}/delete-own-user.dto.ts | 0 libs/common/src/lib/dtos/index.ts | 51 +++++++++++++++++++ .../src/lib/dtos}/transfer-balance.dto.ts | 0 .../common/src/lib/dtos}/update-access.dto.ts | 0 .../src/lib/dtos}/update-account.dto.ts | 2 +- .../src/lib/dtos}/update-asset-profile.dto.ts | 2 +- .../lib/dtos}/update-bulk-market-data.dto.ts | 4 +- .../src/lib/dtos}/update-market-data.dto.ts | 0 .../common/src/lib/dtos}/update-order.dto.ts | 2 +- .../lib/dtos}/update-own-access-token.dto.ts | 0 .../src/lib/dtos}/update-platform.dto.ts | 0 .../src/lib/dtos/update-property.dto.ts | 2 +- .../common/src/lib/dtos}/update-tag.dto.ts | 0 .../src/lib/dtos}/update-user-setting.dto.ts | 2 +- libs/common/src/lib/interfaces/index.ts | 16 +++++- .../interfaces/rule-settings.interface.ts | 0 .../interfaces/simplewebauthn.interface.ts | 0 .../lib}/interfaces/user-item.interface.ts | 0 libs/common/src/lib/pipes/index.ts | 3 ++ .../common/src/lib/pipes}/symbol.pipe.ts | 0 .../src/lib}/validators/is-currency-code.ts | 0 .../account-balances.component.ts | 2 +- .../activities-filter.component.ts | 3 +- .../activities-table.component.stories.ts | 2 +- .../activities-table.component.ts | 2 +- .../assistant-list-item.component.ts | 3 +- ...historical-market-data-editor.component.ts | 2 +- .../portfolio-filter-form.component.ts | 3 +- .../symbol-autocomplete.component.ts | 2 +- .../top-holdings/top-holdings.component.ts | 3 +- 98 files changed, 241 insertions(+), 209 deletions(-) delete mode 100644 apps/api/src/app/endpoints/market-data/update-bulk-market-data.dto.ts rename {apps/api/src/app/auth-device => libs/common/src/lib/dtos}/auth-device.dto.ts (100%) rename {apps/api/src/app/access => libs/common/src/lib/dtos}/create-access.dto.ts (100%) rename {apps/api/src/app/account-balance => libs/common/src/lib/dtos}/create-account-balance.dto.ts (100%) rename {apps/api/src/app/import => libs/common/src/lib/dtos}/create-account-with-balances.dto.ts (75%) rename {apps/api/src/app/account => libs/common/src/lib/dtos}/create-account.dto.ts (89%) rename {apps/api/src/app/import => libs/common/src/lib/dtos}/create-asset-profile-with-market-data.dto.ts (85%) rename {apps/api/src/app/admin => libs/common/src/lib/dtos}/create-asset-profile.dto.ts (94%) rename {apps/api/src/app/order => libs/common/src/lib/dtos}/create-order.dto.ts (94%) rename {apps/api/src/app/platform => libs/common/src/lib/dtos}/create-platform.dto.ts (100%) rename {apps/api/src/app/endpoints/tags => libs/common/src/lib/dtos}/create-tag.dto.ts (100%) rename {apps/api/src/app/endpoints/watchlist => libs/common/src/lib/dtos}/create-watchlist-item.dto.ts (100%) rename {apps/api/src/app/user => libs/common/src/lib/dtos}/delete-own-user.dto.ts (100%) create mode 100644 libs/common/src/lib/dtos/index.ts rename {apps/api/src/app/account => libs/common/src/lib/dtos}/transfer-balance.dto.ts (100%) rename {apps/api/src/app/access => libs/common/src/lib/dtos}/update-access.dto.ts (100%) rename {apps/api/src/app/account => libs/common/src/lib/dtos}/update-account.dto.ts (89%) rename {apps/api/src/app/admin => libs/common/src/lib/dtos}/update-asset-profile.dto.ts (93%) rename {apps/api/src/app/admin => libs/common/src/lib/dtos}/update-bulk-market-data.dto.ts (79%) rename {apps/api/src/app/admin => libs/common/src/lib/dtos}/update-market-data.dto.ts (100%) rename {apps/api/src/app/order => libs/common/src/lib/dtos}/update-order.dto.ts (94%) rename {apps/api/src/app/user => libs/common/src/lib/dtos}/update-own-access-token.dto.ts (100%) rename {apps/api/src/app/platform => libs/common/src/lib/dtos}/update-platform.dto.ts (100%) rename apps/api/src/services/property/property.dto.ts => libs/common/src/lib/dtos/update-property.dto.ts (76%) rename {apps/api/src/app/endpoints/tags => libs/common/src/lib/dtos}/update-tag.dto.ts (100%) rename {apps/api/src/app/user => libs/common/src/lib/dtos}/update-user-setting.dto.ts (96%) rename {apps/api/src/models => libs/common/src/lib}/interfaces/rule-settings.interface.ts (100%) rename apps/api/src/app/auth/interfaces/simplewebauthn.ts => libs/common/src/lib/interfaces/simplewebauthn.interface.ts (100%) rename {apps/api/src/app/user => libs/common/src/lib}/interfaces/user-item.interface.ts (100%) create mode 100644 libs/common/src/lib/pipes/index.ts rename {apps/client/src/app/pipes/symbol => libs/common/src/lib/pipes}/symbol.pipe.ts (100%) rename {apps/api/src => libs/common/src/lib}/validators/is-currency-code.ts (100%) diff --git a/apps/api/src/app/access/access.controller.ts b/apps/api/src/app/access/access.controller.ts index cb1e2d4af..5056a6d71 100644 --- a/apps/api/src/app/access/access.controller.ts +++ b/apps/api/src/app/access/access.controller.ts @@ -1,6 +1,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; import { Access } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -23,8 +24,6 @@ import { Access as AccessModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AccessService } from './access.service'; -import { CreateAccessDto } from './create-access.dto'; -import { UpdateAccessDto } from './update-access.dto'; @Controller('access') export class AccessController { diff --git a/apps/api/src/app/account-balance/account-balance.controller.ts b/apps/api/src/app/account-balance/account-balance.controller.ts index bc454c5ab..baf002bd3 100644 --- a/apps/api/src/app/account-balance/account-balance.controller.ts +++ b/apps/api/src/app/account-balance/account-balance.controller.ts @@ -1,6 +1,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; import { permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -20,7 +21,6 @@ import { AccountBalance } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AccountBalanceService } from './account-balance.service'; -import { CreateAccountBalanceDto } from './create-account-balance.dto'; @Controller('account-balance') export class AccountBalanceController { diff --git a/apps/api/src/app/account-balance/account-balance.service.ts b/apps/api/src/app/account-balance/account-balance.service.ts index 10353f4ca..321624003 100644 --- a/apps/api/src/app/account-balance/account-balance.service.ts +++ b/apps/api/src/app/account-balance/account-balance.service.ts @@ -2,6 +2,7 @@ import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed. import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, getSum, resetHours } from '@ghostfolio/common/helper'; import { AccountBalancesResponse, @@ -15,8 +16,6 @@ import { AccountBalance, Prisma } from '@prisma/client'; import { Big } from 'big.js'; import { format, parseISO } from 'date-fns'; -import { CreateAccountBalanceDto } from './create-account-balance.dto'; - @Injectable() export class AccountBalanceService { public constructor( diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index cd6892ab8..542b199fd 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -7,6 +7,11 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; +import { + CreateAccountDto, + TransferBalanceDto, + UpdateAccountDto +} from '@ghostfolio/common/dtos'; import { AccountBalancesResponse, AccountResponse, @@ -36,9 +41,6 @@ import { Account as AccountModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AccountService } from './account.service'; -import { CreateAccountDto } from './create-account.dto'; -import { TransferBalanceDto } from './transfer-balance.dto'; -import { UpdateAccountDto } from './update-account.dto'; @Controller('account') export class AccountController { diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 7ed7f364b..8b5da4965 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -4,7 +4,6 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service'; import { DemoService } from '@ghostfolio/api/services/demo/demo.service'; -import { PropertyDto } from '@ghostfolio/api/services/property/property.dto'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { @@ -13,6 +12,10 @@ import { GATHER_ASSET_PROFILE_PROCESS_JOB_NAME, GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS } from '@ghostfolio/common/config'; +import { + UpdateAssetProfileDto, + UpdatePropertyDto +} from '@ghostfolio/common/dtos'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { AdminData, @@ -52,7 +55,6 @@ import { isDate, parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AdminService } from './admin.service'; -import { UpdateAssetProfileDto } from './update-asset-profile.dto'; @Controller('admin') export class AdminController { @@ -305,7 +307,7 @@ export class AdminController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async updateProperty( @Param('key') key: string, - @Body() data: PropertyDto + @Body() data: UpdatePropertyDto ) { return this.adminService.putSetting(key, data.value); } diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index 57fd04bc7..b45e7b97b 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -2,7 +2,11 @@ import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service'; 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'; -import { OAuthResponse } from '@ghostfolio/common/interfaces'; +import { + AssertionCredentialJSON, + AttestationCredentialJSON, + OAuthResponse +} from '@ghostfolio/common/interfaces'; import { Body, @@ -22,10 +26,6 @@ import { Request, Response } from 'express'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; import { AuthService } from './auth.service'; -import { - AssertionCredentialJSON, - AttestationCredentialJSON -} from './interfaces/simplewebauthn'; @Controller('auth') export class AuthController { diff --git a/apps/api/src/app/auth/interfaces/interfaces.ts b/apps/api/src/app/auth/interfaces/interfaces.ts index 45415355e..4fdcc25b5 100644 --- a/apps/api/src/app/auth/interfaces/interfaces.ts +++ b/apps/api/src/app/auth/interfaces/interfaces.ts @@ -1,4 +1,4 @@ -import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto'; +import { AuthDeviceDto } from '@ghostfolio/common/dtos'; import { Provider } from '@prisma/client'; diff --git a/apps/api/src/app/auth/web-auth.service.ts b/apps/api/src/app/auth/web-auth.service.ts index d14ef7798..6cffcd244 100644 --- a/apps/api/src/app/auth/web-auth.service.ts +++ b/apps/api/src/app/auth/web-auth.service.ts @@ -1,7 +1,11 @@ -import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto'; import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { AuthDeviceDto } from '@ghostfolio/common/dtos'; +import { + AssertionCredentialJSON, + AttestationCredentialJSON +} from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -27,11 +31,6 @@ import { import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers'; import ms from 'ms'; -import { - AssertionCredentialJSON, - AttestationCredentialJSON -} from './interfaces/simplewebauthn'; - @Injectable() export class WebAuthService { public constructor( 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 4843536da..987d34918 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 @@ -10,6 +10,7 @@ import { ghostfolioFearAndGreedIndexSymbolCryptocurrencies, ghostfolioFearAndGreedIndexSymbolStocks } from '@ghostfolio/common/config'; +import { UpdateBulkMarketDataDto } from '@ghostfolio/common/dtos'; import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; import { MarketDataDetailsResponse, @@ -35,8 +36,6 @@ import { DataSource, Prisma } from '@prisma/client'; import { parseISO } from 'date-fns'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; -import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto'; - @Controller('market-data') export class MarketDataController { public constructor( diff --git a/apps/api/src/app/endpoints/market-data/update-bulk-market-data.dto.ts b/apps/api/src/app/endpoints/market-data/update-bulk-market-data.dto.ts deleted file mode 100644 index d07b189b2..000000000 --- a/apps/api/src/app/endpoints/market-data/update-bulk-market-data.dto.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Type } from 'class-transformer'; -import { - ArrayNotEmpty, - IsArray, - IsISO8601, - IsNumber, - IsOptional -} from 'class-validator'; - -export class UpdateBulkMarketDataDto { - @ArrayNotEmpty() - @IsArray() - @Type(() => UpdateMarketDataDto) - marketData: UpdateMarketDataDto[]; -} - -class UpdateMarketDataDto { - @IsISO8601() - @IsOptional() - date?: string; - - @IsNumber() - marketPrice: number; -} diff --git a/apps/api/src/app/endpoints/tags/tags.controller.ts b/apps/api/src/app/endpoints/tags/tags.controller.ts index bf216bb21..925e1e0ed 100644 --- a/apps/api/src/app/endpoints/tags/tags.controller.ts +++ b/apps/api/src/app/endpoints/tags/tags.controller.ts @@ -1,6 +1,7 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; +import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { RequestWithUser } from '@ghostfolio/common/types'; @@ -21,9 +22,6 @@ import { AuthGuard } from '@nestjs/passport'; import { Tag } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { CreateTagDto } from './create-tag.dto'; -import { UpdateTagDto } from './update-tag.dto'; - @Controller('tags') export class TagsController { public constructor( diff --git a/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts b/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts index 2a8ea9875..78693239a 100644 --- a/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts +++ b/apps/api/src/app/endpoints/watchlist/watchlist.controller.ts @@ -4,6 +4,7 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; +import { CreateWatchlistItemDto } from '@ghostfolio/common/dtos'; import { WatchlistResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import { RequestWithUser } from '@ghostfolio/common/types'; @@ -26,7 +27,6 @@ import { AuthGuard } from '@nestjs/passport'; import { DataSource } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; import { WatchlistService } from './watchlist.service'; @Controller('watchlist') diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index 330bc52f6..bf45c7cda 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -1,12 +1,13 @@ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; +import { + CreateAccountWithBalancesDto, + CreateAssetProfileWithMarketDataDto, + CreateOrderDto, + CreateTagDto +} from '@ghostfolio/common/dtos'; import { Type } from 'class-transformer'; import { IsArray, IsOptional, ValidateNested } from 'class-validator'; -import { CreateTagDto } from '../endpoints/tags/create-tag.dto'; -import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto'; -import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto'; - export class ImportDataDto { @IsArray() @IsOptional() diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index cac466192..a5f3dda96 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -1,6 +1,4 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -11,6 +9,11 @@ import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathe import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config'; +import { + CreateAssetProfileDto, + CreateAccountDto, + CreateOrderDto +} from '@ghostfolio/common/dtos'; import { getAssetProfileIdentifier, parseDate @@ -34,7 +37,6 @@ import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns'; import { omit, uniqBy } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { CreateAssetProfileDto } from '../admin/create-asset-profile.dto'; import { ImportDataDto } from './import-data.dto'; @Injectable() diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index d6c231059..962558315 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -11,6 +11,7 @@ import { DATA_GATHERING_QUEUE_PRIORITY_HIGH, HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; +import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { ActivitiesResponse, ActivityResponse @@ -39,9 +40,7 @@ import { Order as OrderModel, Prisma } from '@prisma/client'; import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { CreateOrderDto } from './create-order.dto'; import { OrderService } from './order.service'; -import { UpdateOrderDto } from './update-order.dto'; @Controller('order') export class OrderController { diff --git a/apps/api/src/app/platform/platform.controller.ts b/apps/api/src/app/platform/platform.controller.ts index c91f58cf8..2d4a1d413 100644 --- a/apps/api/src/app/platform/platform.controller.ts +++ b/apps/api/src/app/platform/platform.controller.ts @@ -1,5 +1,6 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { permissions } from '@ghostfolio/common/permissions'; import { @@ -17,9 +18,7 @@ import { AuthGuard } from '@nestjs/passport'; import { Platform } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { CreatePlatformDto } from './create-platform.dto'; import { PlatformService } from './platform.service'; -import { UpdatePlatformDto } from './update-platform.dto'; @Controller('platform') export class PlatformController { diff --git a/apps/api/src/app/portfolio/rules.service.ts b/apps/api/src/app/portfolio/rules.service.ts index 48d1658aa..5bfb116e0 100644 --- a/apps/api/src/app/portfolio/rules.service.ts +++ b/apps/api/src/app/portfolio/rules.service.ts @@ -1,7 +1,7 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { PortfolioReportRule, + RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 8704662f7..397ae016b 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -3,9 +3,15 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { + DeleteOwnUserDto, + UpdateOwnAccessTokenDto, + UpdateUserSettingDto +} from '@ghostfolio/common/dtos'; import { AccessTokenResponse, User, + UserItem, UserSettings } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -31,10 +37,6 @@ import { User as UserModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { merge, size } from 'lodash'; -import { DeleteOwnUserDto } from './delete-own-user.dto'; -import { UserItem } from './interfaces/user-item.interface'; -import { UpdateOwnAccessTokenDto } from './update-own-access-token.dto'; -import { UpdateUserSettingDto } from './update-user-setting.dto'; import { UserService } from './user.service'; @Controller('user') diff --git a/apps/api/src/models/interfaces/rule.interface.ts b/apps/api/src/models/interfaces/rule.interface.ts index 5dcd42317..7c794614e 100644 --- a/apps/api/src/models/interfaces/rule.interface.ts +++ b/apps/api/src/models/interfaces/rule.interface.ts @@ -1,5 +1,4 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; -import { UserSettings } from '@ghostfolio/common/interfaces'; +import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; import { EvaluationResult } from './evaluation-result.interface'; diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts index 52491a0b7..9c27e0018 100644 --- a/apps/api/src/models/rule.ts +++ b/apps/api/src/models/rule.ts @@ -1,10 +1,10 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; import { groupBy } from '@ghostfolio/common/helper'; import { PortfolioPosition, PortfolioReportRule, + RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts index 51c808b25..0004d394e 100644 --- a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts @@ -1,8 +1,11 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { PortfolioDetails, UserSettings } from '@ghostfolio/common/interfaces'; +import { + PortfolioDetails, + RuleSettings, + UserSettings +} from '@ghostfolio/common/interfaces'; import { Account } from '@prisma/client'; diff --git a/apps/api/src/models/rules/account-cluster-risk/single-account.ts b/apps/api/src/models/rules/account-cluster-risk/single-account.ts index 0e07a9dc6..9988ea3cc 100644 --- a/apps/api/src/models/rules/account-cluster-risk/single-account.ts +++ b/apps/api/src/models/rules/account-cluster-risk/single-account.ts @@ -1,8 +1,11 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { PortfolioDetails, UserSettings } from '@ghostfolio/common/interfaces'; +import { + PortfolioDetails, + RuleSettings, + UserSettings +} from '@ghostfolio/common/interfaces'; export class AccountClusterRiskSingleAccount extends Rule { private accounts: PortfolioDetails['accounts']; diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts b/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts index 9a6f9dacb..f70756e91 100644 --- a/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts +++ b/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts @@ -1,8 +1,11 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; +import { + PortfolioPosition, + RuleSettings, + UserSettings +} from '@ghostfolio/common/interfaces'; export class AssetClassClusterRiskEquity extends Rule { private holdings: PortfolioPosition[]; diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts index 70cdb63c8..3bd835e4d 100644 --- a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts +++ b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts @@ -1,8 +1,11 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; +import { + PortfolioPosition, + RuleSettings, + UserSettings +} from '@ghostfolio/common/interfaces'; export class AssetClassClusterRiskFixedIncome extends Rule { private holdings: PortfolioPosition[]; diff --git a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts index 273c98e35..d3176582f 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts @@ -1,8 +1,11 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; +import { + PortfolioPosition, + RuleSettings, + UserSettings +} from '@ghostfolio/common/interfaces'; export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { private holdings: PortfolioPosition[]; diff --git a/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts index b09b7f3ef..c73160b52 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts @@ -1,8 +1,11 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; +import { + PortfolioPosition, + RuleSettings, + UserSettings +} from '@ghostfolio/common/interfaces'; export class CurrencyClusterRiskCurrentInvestment extends Rule { private holdings: PortfolioPosition[]; diff --git a/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts index fa4f80d40..df9b78eef 100644 --- a/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts @@ -1,8 +1,7 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; +import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; export class EconomicMarketClusterRiskDevelopedMarkets extends Rule { private currentValueInBaseCurrency: number; diff --git a/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts index 1414b53ed..4583dc50a 100644 --- a/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts @@ -1,8 +1,7 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; +import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; export class EconomicMarketClusterRiskEmergingMarkets extends Rule { private currentValueInBaseCurrency: number; diff --git a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts index 2129f438b..b956263f8 100644 --- a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts +++ b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts @@ -1,8 +1,7 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; +import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; export class EmergencyFundSetup extends Rule { private emergencyFund: number; diff --git a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts index c5448a277..cb85a73ba 100644 --- a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts +++ b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts @@ -1,8 +1,7 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; +import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; export class FeeRatioInitialInvestment extends Rule { private fees: number; diff --git a/apps/api/src/models/rules/liquidity/buying-power.ts b/apps/api/src/models/rules/liquidity/buying-power.ts index 2cd4d6fee..541750d7e 100644 --- a/apps/api/src/models/rules/liquidity/buying-power.ts +++ b/apps/api/src/models/rules/liquidity/buying-power.ts @@ -1,8 +1,7 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; +import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; export class BuyingPower extends Rule { private buyingPower: number; diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/interfaces/rule-settings.interface.ts b/apps/api/src/models/rules/regional-market-cluster-risk/interfaces/rule-settings.interface.ts index 8b9fddf3a..621b4df0b 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/interfaces/rule-settings.interface.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/interfaces/rule-settings.interface.ts @@ -1,4 +1,4 @@ -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; +import { RuleSettings } from '@ghostfolio/common/interfaces'; export interface Settings extends RuleSettings { baseCurrency: string; diff --git a/apps/api/src/services/market-data/market-data.service.ts b/apps/api/src/services/market-data/market-data.service.ts index d318b9a70..87b08e1bd 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -1,7 +1,7 @@ -import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface'; import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; import { resetHours } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index 47ba48f4e..ceae50f01 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -1,11 +1,10 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; +import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { AccountBalancesResponse, diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 2b96bda3b..4f1b60981 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -1,4 +1,3 @@ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -15,6 +14,7 @@ import { } from '@ghostfolio/common/interfaces'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 83b2586ce..9969a59ba 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; @@ -10,6 +8,7 @@ import { ASSET_CLASS_MAPPING, PROPERTY_IS_DATA_GATHERING_ENABLED } from '@ghostfolio/common/config'; +import { UpdateAssetProfileDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails, diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 76d00bb10..1dd150ac5 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,11 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; -import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { diff --git a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts index 0d9e6f8bd..23e6ca271 100644 --- a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts +++ b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts @@ -1,7 +1,5 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; -import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; +import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index 4fd34acc0..6b79b8fe6 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,10 +1,8 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; -import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts index 2d1babeb4..487a4d498 100644 --- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts +++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts @@ -1,7 +1,5 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; -import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; +import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 03d53e058..9fb9a8351 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces'; import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component'; import { LayoutService } from '@ghostfolio/client/core/layout.service'; @@ -12,6 +10,7 @@ import { } from '@ghostfolio/client/services/settings-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { UpdateUserSettingDto } from '@ghostfolio/common/dtos'; import { Filter, InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index a6c02f7dc..caca0c2bc 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { DataService } from '@ghostfolio/client/services/data.service'; @@ -9,6 +7,7 @@ import { NUMERICAL_PRECISION_THRESHOLD_5_FIGURES, NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; +import { CreateOrderDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { Activity, diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index 9b40f8f50..e2ffc1cf6 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -1,8 +1,7 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; -import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; +import { UpdateUserSettingDto } from '@ghostfolio/common/dtos'; import { PortfolioReportRule, + RuleSettings, XRayRulesSettings } from '@ghostfolio/common/interfaces'; diff --git a/apps/client/src/app/components/rules/rules.component.ts b/apps/client/src/app/components/rules/rules.component.ts index 7dd322c21..22e1718f8 100644 --- a/apps/client/src/app/components/rules/rules.component.ts +++ b/apps/client/src/app/components/rules/rules.component.ts @@ -1,6 +1,5 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { GfRuleComponent } from '@ghostfolio/client/components/rule/rule.component'; +import { UpdateUserSettingDto } from '@ghostfolio/common/dtos'; import { PortfolioReportRule, XRayRulesSettings diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 9c21c4f34..be0842467 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -1,9 +1,7 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; -import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; +import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/user-account-access/user-account-access.component.ts b/apps/client/src/app/components/user-account-access/user-account-access.component.ts index de2483e50..38d34a4e2 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,11 +1,10 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { CreateAccessDto } from '@ghostfolio/common/dtos'; import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 010d727c6..2bb6457a5 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -1,13 +1,14 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; -import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto'; -import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; import { GfAccountDetailDialogComponent } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component'; import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { + CreateAccountDto, + TransferBalanceDto, + UpdateAccountDto +} from '@ghostfolio/common/dtos'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index 8df990d3d..08ecbf15a 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -1,8 +1,6 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; -import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; +import { CreateAccountDto, UpdateAccountDto } from '@ghostfolio/common/dtos'; import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; diff --git a/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts b/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts index 4af1dbe6f..85d2e60bf 100644 --- a/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts @@ -1,5 +1,4 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto'; +import { TransferBalanceDto } from '@ghostfolio/common/dtos'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index d6a1540d0..5b5273b65 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -1,11 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; import { IcsService } from '@ghostfolio/client/services/ics/ics.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; +import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { downloadAsFile } from '@ghostfolio/common/helper'; import { Activity, 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 4d2f958e7..3aedb8d73 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 @@ -1,8 +1,6 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ASSET_CLASS_MAPPING } from '@ghostfolio/common/config'; +import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { AssetClassSelectorOption, 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 e2b1403c0..a3d7d326d 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 @@ -1,14 +1,15 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; -import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; -import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive'; -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; +import { + CreateAccountWithBalancesDto, + CreateAssetProfileWithMarketDataDto, + CreateTagDto +} from '@ghostfolio/common/dtos'; import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts index bbd50a0e1..0bf869238 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -1,9 +1,8 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { GfRulesComponent } from '@ghostfolio/client/components/rules/rules.component'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { UpdateUserSettingDto } from '@ghostfolio/common/dtos'; import { PortfolioReportResponse, PortfolioReportRule diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 68a02facc..10804aac9 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -1,12 +1,13 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; -import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; -import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto'; import { + DEFAULT_PAGE_SIZE, HEADER_KEY_SKIP_INTERCEPTOR, HEADER_KEY_TOKEN } from '@ghostfolio/common/config'; -import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; +import { + CreatePlatformDto, + UpdateAssetProfileDto, + UpdatePlatformDto +} from '@ghostfolio/common/dtos'; import { AdminData, AdminJobs, diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 60118d205..4c324fe03 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -1,21 +1,21 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; -import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto'; -import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto'; -import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; -import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto'; -import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; -import { UpdateBulkMarketDataDto } from '@ghostfolio/api/app/admin/update-bulk-market-data.dto'; -import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; -import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto'; -import { CreateWatchlistItemDto } from '@ghostfolio/api/app/endpoints/watchlist/create-watchlist-item.dto'; -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; -import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; -import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto'; -import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface'; -import { UpdateOwnAccessTokenDto } from '@ghostfolio/api/app/user/update-own-access-token.dto'; -import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; -import { PropertyDto } from '@ghostfolio/api/services/property/property.dto'; +import { + CreateAccessDto, + CreateAccountBalanceDto, + CreateAccountDto, + CreateOrderDto, + CreateTagDto, + CreateWatchlistItemDto, + DeleteOwnUserDto, + TransferBalanceDto, + UpdateAccessDto, + UpdateAccountDto, + UpdateBulkMarketDataDto, + UpdateOrderDto, + UpdateOwnAccessTokenDto, + UpdatePropertyDto, + UpdateTagDto, + UpdateUserSettingDto +} from '@ghostfolio/common/dtos'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Access, @@ -52,6 +52,7 @@ import { PublicPortfolioResponse, SymbolItem, User, + UserItem, WatchlistResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; @@ -808,7 +809,7 @@ export class DataService { return this.http.put(`/api/v1/account/${aAccount.id}`, aAccount); } - public putAdminSetting(key: string, aData: PropertyDto) { + public putAdminSetting(key: string, aData: UpdatePropertyDto) { return this.http.put(`/api/v1/admin/settings/${key}`, aData); } diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 607b8a0a0..94d8470f7 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -1,8 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto'; -import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto'; -import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto'; -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; +import { + CreateAccountWithBalancesDto, + CreateAssetProfileWithMarketDataDto, + CreateOrderDto, + CreateTagDto +} from '@ghostfolio/common/dtos'; import { parseDate as parseDateHelper } from '@ghostfolio/common/helper'; import { Activity } from '@ghostfolio/common/interfaces'; diff --git a/apps/client/src/app/services/web-authn.service.ts b/apps/client/src/app/services/web-authn.service.ts index 9ace943b6..95c264310 100644 --- a/apps/client/src/app/services/web-authn.service.ts +++ b/apps/client/src/app/services/web-authn.service.ts @@ -1,10 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto'; +import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service'; +import { AuthDeviceDto } from '@ghostfolio/common/dtos'; import { PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON -} from '@ghostfolio/api/app/auth/interfaces/simplewebauthn'; -import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service'; +} from '@ghostfolio/common/interfaces'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; diff --git a/apps/api/src/app/auth-device/auth-device.dto.ts b/libs/common/src/lib/dtos/auth-device.dto.ts similarity index 100% rename from apps/api/src/app/auth-device/auth-device.dto.ts rename to libs/common/src/lib/dtos/auth-device.dto.ts diff --git a/apps/api/src/app/access/create-access.dto.ts b/libs/common/src/lib/dtos/create-access.dto.ts similarity index 100% rename from apps/api/src/app/access/create-access.dto.ts rename to libs/common/src/lib/dtos/create-access.dto.ts diff --git a/apps/api/src/app/account-balance/create-account-balance.dto.ts b/libs/common/src/lib/dtos/create-account-balance.dto.ts similarity index 100% rename from apps/api/src/app/account-balance/create-account-balance.dto.ts rename to libs/common/src/lib/dtos/create-account-balance.dto.ts diff --git a/apps/api/src/app/import/create-account-with-balances.dto.ts b/libs/common/src/lib/dtos/create-account-with-balances.dto.ts similarity index 75% rename from apps/api/src/app/import/create-account-with-balances.dto.ts rename to libs/common/src/lib/dtos/create-account-with-balances.dto.ts index 6cf4057f8..53740f0f9 100644 --- a/apps/api/src/app/import/create-account-with-balances.dto.ts +++ b/libs/common/src/lib/dtos/create-account-with-balances.dto.ts @@ -1,4 +1,4 @@ -import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; +import { CreateAccountDto } from '@ghostfolio/common/dtos'; import { AccountBalance } from '@ghostfolio/common/interfaces'; import { IsArray, IsOptional } from 'class-validator'; diff --git a/apps/api/src/app/account/create-account.dto.ts b/libs/common/src/lib/dtos/create-account.dto.ts similarity index 89% rename from apps/api/src/app/account/create-account.dto.ts rename to libs/common/src/lib/dtos/create-account.dto.ts index b331d4ec7..fa88580f1 100644 --- a/apps/api/src/app/account/create-account.dto.ts +++ b/libs/common/src/lib/dtos/create-account.dto.ts @@ -1,4 +1,4 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { Transform, TransformFnParams } from 'class-transformer'; import { diff --git a/apps/api/src/app/import/create-asset-profile-with-market-data.dto.ts b/libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts similarity index 85% rename from apps/api/src/app/import/create-asset-profile-with-market-data.dto.ts rename to libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts index fd90ab1af..04611371d 100644 --- a/apps/api/src/app/import/create-asset-profile-with-market-data.dto.ts +++ b/libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts @@ -3,7 +3,7 @@ import { MarketData } from '@ghostfolio/common/interfaces'; import { DataSource } from '@prisma/client'; import { IsArray, IsEnum, IsOptional } from 'class-validator'; -import { CreateAssetProfileDto } from '../admin/create-asset-profile.dto'; +import { CreateAssetProfileDto } from './create-asset-profile.dto'; export class CreateAssetProfileWithMarketDataDto extends CreateAssetProfileDto { @IsEnum([DataSource.MANUAL], { diff --git a/apps/api/src/app/admin/create-asset-profile.dto.ts b/libs/common/src/lib/dtos/create-asset-profile.dto.ts similarity index 94% rename from apps/api/src/app/admin/create-asset-profile.dto.ts rename to libs/common/src/lib/dtos/create-asset-profile.dto.ts index 8041b0f0e..80d45ba42 100644 --- a/apps/api/src/app/admin/create-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/create-asset-profile.dto.ts @@ -1,4 +1,4 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; import { diff --git a/apps/api/src/app/order/create-order.dto.ts b/libs/common/src/lib/dtos/create-order.dto.ts similarity index 94% rename from apps/api/src/app/order/create-order.dto.ts rename to libs/common/src/lib/dtos/create-order.dto.ts index fb4ac32dd..dfd0d8aa5 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/libs/common/src/lib/dtos/create-order.dto.ts @@ -1,5 +1,5 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; import { Transform, TransformFnParams } from 'class-transformer'; diff --git a/apps/api/src/app/platform/create-platform.dto.ts b/libs/common/src/lib/dtos/create-platform.dto.ts similarity index 100% rename from apps/api/src/app/platform/create-platform.dto.ts rename to libs/common/src/lib/dtos/create-platform.dto.ts diff --git a/apps/api/src/app/endpoints/tags/create-tag.dto.ts b/libs/common/src/lib/dtos/create-tag.dto.ts similarity index 100% rename from apps/api/src/app/endpoints/tags/create-tag.dto.ts rename to libs/common/src/lib/dtos/create-tag.dto.ts diff --git a/apps/api/src/app/endpoints/watchlist/create-watchlist-item.dto.ts b/libs/common/src/lib/dtos/create-watchlist-item.dto.ts similarity index 100% rename from apps/api/src/app/endpoints/watchlist/create-watchlist-item.dto.ts rename to libs/common/src/lib/dtos/create-watchlist-item.dto.ts diff --git a/apps/api/src/app/user/delete-own-user.dto.ts b/libs/common/src/lib/dtos/delete-own-user.dto.ts similarity index 100% rename from apps/api/src/app/user/delete-own-user.dto.ts rename to libs/common/src/lib/dtos/delete-own-user.dto.ts diff --git a/libs/common/src/lib/dtos/index.ts b/libs/common/src/lib/dtos/index.ts new file mode 100644 index 000000000..3631d6eae --- /dev/null +++ b/libs/common/src/lib/dtos/index.ts @@ -0,0 +1,51 @@ +import { AuthDeviceDto } from './auth-device.dto'; +import { CreateAccessDto } from './create-access.dto'; +import { CreateAccountBalanceDto } from './create-account-balance.dto'; +import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto'; +import { CreateAccountDto } from './create-account.dto'; +import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto'; +import { CreateAssetProfileDto } from './create-asset-profile.dto'; +import { CreateOrderDto } from './create-order.dto'; +import { CreatePlatformDto } from './create-platform.dto'; +import { CreateTagDto } from './create-tag.dto'; +import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; +import { DeleteOwnUserDto } from './delete-own-user.dto'; +import { TransferBalanceDto } from './transfer-balance.dto'; +import { UpdateAccessDto } from './update-access.dto'; +import { UpdateAccountDto } from './update-account.dto'; +import { UpdateAssetProfileDto } from './update-asset-profile.dto'; +import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto'; +import { UpdateMarketDataDto } from './update-market-data.dto'; +import { UpdateOrderDto } from './update-order.dto'; +import { UpdateOwnAccessTokenDto } from './update-own-access-token.dto'; +import { UpdatePlatformDto } from './update-platform.dto'; +import { UpdatePropertyDto } from './update-property.dto'; +import { UpdateTagDto } from './update-tag.dto'; +import { UpdateUserSettingDto } from './update-user-setting.dto'; + +export { + AuthDeviceDto, + CreateAccessDto, + CreateAccountBalanceDto, + CreateAccountDto, + CreateAccountWithBalancesDto, + CreateAssetProfileDto, + CreateAssetProfileWithMarketDataDto, + CreateOrderDto, + CreatePlatformDto, + CreateTagDto, + CreateWatchlistItemDto, + DeleteOwnUserDto, + TransferBalanceDto, + UpdateAccessDto, + UpdateAccountDto, + UpdateAssetProfileDto, + UpdateBulkMarketDataDto, + UpdateMarketDataDto, + UpdateOrderDto, + UpdateOwnAccessTokenDto, + UpdatePlatformDto, + UpdatePropertyDto, + UpdateTagDto, + UpdateUserSettingDto +}; diff --git a/apps/api/src/app/account/transfer-balance.dto.ts b/libs/common/src/lib/dtos/transfer-balance.dto.ts similarity index 100% rename from apps/api/src/app/account/transfer-balance.dto.ts rename to libs/common/src/lib/dtos/transfer-balance.dto.ts diff --git a/apps/api/src/app/access/update-access.dto.ts b/libs/common/src/lib/dtos/update-access.dto.ts similarity index 100% rename from apps/api/src/app/access/update-access.dto.ts rename to libs/common/src/lib/dtos/update-access.dto.ts diff --git a/apps/api/src/app/account/update-account.dto.ts b/libs/common/src/lib/dtos/update-account.dto.ts similarity index 89% rename from apps/api/src/app/account/update-account.dto.ts rename to libs/common/src/lib/dtos/update-account.dto.ts index 3a721d873..066bacbfd 100644 --- a/apps/api/src/app/account/update-account.dto.ts +++ b/libs/common/src/lib/dtos/update-account.dto.ts @@ -1,4 +1,4 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { Transform, TransformFnParams } from 'class-transformer'; import { diff --git a/apps/api/src/app/admin/update-asset-profile.dto.ts b/libs/common/src/lib/dtos/update-asset-profile.dto.ts similarity index 93% rename from apps/api/src/app/admin/update-asset-profile.dto.ts rename to libs/common/src/lib/dtos/update-asset-profile.dto.ts index 5056dccdb..43f5aa617 100644 --- a/apps/api/src/app/admin/update-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/update-asset-profile.dto.ts @@ -1,4 +1,4 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client'; import { diff --git a/apps/api/src/app/admin/update-bulk-market-data.dto.ts b/libs/common/src/lib/dtos/update-bulk-market-data.dto.ts similarity index 79% rename from apps/api/src/app/admin/update-bulk-market-data.dto.ts rename to libs/common/src/lib/dtos/update-bulk-market-data.dto.ts index da0da1272..f92112f24 100644 --- a/apps/api/src/app/admin/update-bulk-market-data.dto.ts +++ b/libs/common/src/lib/dtos/update-bulk-market-data.dto.ts @@ -1,8 +1,8 @@ +import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; + import { Type } from 'class-transformer'; import { ArrayNotEmpty, IsArray } from 'class-validator'; -import { UpdateMarketDataDto } from './update-market-data.dto'; - export class UpdateBulkMarketDataDto { @ArrayNotEmpty() @IsArray() diff --git a/apps/api/src/app/admin/update-market-data.dto.ts b/libs/common/src/lib/dtos/update-market-data.dto.ts similarity index 100% rename from apps/api/src/app/admin/update-market-data.dto.ts rename to libs/common/src/lib/dtos/update-market-data.dto.ts diff --git a/apps/api/src/app/order/update-order.dto.ts b/libs/common/src/lib/dtos/update-order.dto.ts similarity index 94% rename from apps/api/src/app/order/update-order.dto.ts rename to libs/common/src/lib/dtos/update-order.dto.ts index 25c92532a..3656a8043 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/libs/common/src/lib/dtos/update-order.dto.ts @@ -1,5 +1,5 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client'; import { Transform, TransformFnParams } from 'class-transformer'; diff --git a/apps/api/src/app/user/update-own-access-token.dto.ts b/libs/common/src/lib/dtos/update-own-access-token.dto.ts similarity index 100% rename from apps/api/src/app/user/update-own-access-token.dto.ts rename to libs/common/src/lib/dtos/update-own-access-token.dto.ts diff --git a/apps/api/src/app/platform/update-platform.dto.ts b/libs/common/src/lib/dtos/update-platform.dto.ts similarity index 100% rename from apps/api/src/app/platform/update-platform.dto.ts rename to libs/common/src/lib/dtos/update-platform.dto.ts diff --git a/apps/api/src/services/property/property.dto.ts b/libs/common/src/lib/dtos/update-property.dto.ts similarity index 76% rename from apps/api/src/services/property/property.dto.ts rename to libs/common/src/lib/dtos/update-property.dto.ts index 037b4703c..77115759a 100644 --- a/apps/api/src/services/property/property.dto.ts +++ b/libs/common/src/lib/dtos/update-property.dto.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString } from 'class-validator'; -export class PropertyDto { +export class UpdatePropertyDto { @IsOptional() @IsString() value: string; diff --git a/apps/api/src/app/endpoints/tags/update-tag.dto.ts b/libs/common/src/lib/dtos/update-tag.dto.ts similarity index 100% rename from apps/api/src/app/endpoints/tags/update-tag.dto.ts rename to libs/common/src/lib/dtos/update-tag.dto.ts diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/libs/common/src/lib/dtos/update-user-setting.dto.ts similarity index 96% rename from apps/api/src/app/user/update-user-setting.dto.ts rename to libs/common/src/lib/dtos/update-user-setting.dto.ts index 3ee59f7dd..cf7dff7e8 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/libs/common/src/lib/dtos/update-user-setting.dto.ts @@ -1,4 +1,3 @@ -import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { XRayRulesSettings } from '@ghostfolio/common/interfaces'; import type { ColorScheme, @@ -6,6 +5,7 @@ import type { HoldingsViewMode, ViewMode } from '@ghostfolio/common/types'; +import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code'; import { IsArray, diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index c47af2d97..1d7991e40 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -68,7 +68,7 @@ import type { MarketDataDetailsResponse } from './responses/market-data-details- import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface'; -import { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface'; +import type { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface'; import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioInvestmentsResponse } from './responses/portfolio-investments.interface'; import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; @@ -76,7 +76,14 @@ import type { PortfolioReportResponse } from './responses/portfolio-report.inter import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface'; import type { QuotesResponse } from './responses/quotes-response.interface'; import type { WatchlistResponse } from './responses/watchlist-response.interface'; +import type { RuleSettings } from './rule-settings.interface'; import type { ScraperConfiguration } from './scraper-configuration.interface'; +import type { + AssertionCredentialJSON, + AttestationCredentialJSON, + PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialRequestOptionsJSON +} from './simplewebauthn.interface'; import type { Statistics } from './statistics.interface'; import type { SubscriptionOffer } from './subscription-offer.interface'; import type { SymbolItem } from './symbol-item.interface'; @@ -84,6 +91,7 @@ import type { SymbolMetrics } from './symbol-metrics.interface'; import type { SystemMessage } from './system-message.interface'; import type { TabConfiguration } from './tab-configuration.interface'; import type { ToggleOption } from './toggle-option.interface'; +import type { UserItem } from './user-item.interface'; import type { UserSettings } from './user-settings.interface'; import type { User } from './user.interface'; import type { XRayRulesSettings } from './x-ray-rules-settings.interface'; @@ -109,9 +117,11 @@ export { AdminUsersResponse, AiPromptResponse, ApiKeyResponse, + AssertionCredentialJSON, AssetClassSelectorOption, AssetProfileIdentifier, AssetResponse, + AttestationCredentialJSON, Benchmark, BenchmarkMarketDataDetailsResponse, BenchmarkProperty, @@ -160,9 +170,12 @@ export { PortfolioSummary, Position, Product, + PublicKeyCredentialCreationOptionsJSON, + PublicKeyCredentialRequestOptionsJSON, PublicPortfolioResponse, QuotesResponse, ResponseError, + RuleSettings, ScraperConfiguration, Statistics, SubscriptionOffer, @@ -172,6 +185,7 @@ export { TabConfiguration, ToggleOption, User, + UserItem, UserSettings, WatchlistResponse, XRayRulesSettings diff --git a/apps/api/src/models/interfaces/rule-settings.interface.ts b/libs/common/src/lib/interfaces/rule-settings.interface.ts similarity index 100% rename from apps/api/src/models/interfaces/rule-settings.interface.ts rename to libs/common/src/lib/interfaces/rule-settings.interface.ts diff --git a/apps/api/src/app/auth/interfaces/simplewebauthn.ts b/libs/common/src/lib/interfaces/simplewebauthn.interface.ts similarity index 100% rename from apps/api/src/app/auth/interfaces/simplewebauthn.ts rename to libs/common/src/lib/interfaces/simplewebauthn.interface.ts diff --git a/apps/api/src/app/user/interfaces/user-item.interface.ts b/libs/common/src/lib/interfaces/user-item.interface.ts similarity index 100% rename from apps/api/src/app/user/interfaces/user-item.interface.ts rename to libs/common/src/lib/interfaces/user-item.interface.ts diff --git a/libs/common/src/lib/pipes/index.ts b/libs/common/src/lib/pipes/index.ts new file mode 100644 index 000000000..7b5ca4bac --- /dev/null +++ b/libs/common/src/lib/pipes/index.ts @@ -0,0 +1,3 @@ +import { GfSymbolPipe } from './symbol.pipe'; + +export { GfSymbolPipe }; diff --git a/apps/client/src/app/pipes/symbol/symbol.pipe.ts b/libs/common/src/lib/pipes/symbol.pipe.ts similarity index 100% rename from apps/client/src/app/pipes/symbol/symbol.pipe.ts rename to libs/common/src/lib/pipes/symbol.pipe.ts diff --git a/apps/api/src/validators/is-currency-code.ts b/libs/common/src/lib/validators/is-currency-code.ts similarity index 100% rename from apps/api/src/validators/is-currency-code.ts rename to libs/common/src/lib/validators/is-currency-code.ts diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index 904e7d46c..679899af9 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -1,8 +1,8 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; +import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, getLocale } from '@ghostfolio/common/helper'; import { AccountBalancesResponse } from '@ghostfolio/common/interfaces'; diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts index 177312490..34f883c67 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts @@ -1,6 +1,5 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { Filter, FilterGroup } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { CommonModule } from '@angular/common'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts index 78e712c89..a0ad690d7 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts @@ -1,5 +1,5 @@ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { Activity } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index 99ba2aded..d7b13a7e8 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -1,7 +1,6 @@ /* eslint-disable @nx/enforce-module-boundaries */ import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { DEFAULT_PAGE_SIZE, TAG_ID_EXCLUDE_FROM_ANALYSIS @@ -11,6 +10,7 @@ import { Activity, AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { SelectionModel } from '@angular/cdk/collections'; diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts index 059bbaf9e..c2ad2462e 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts @@ -1,5 +1,4 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { FocusableOption } from '@angular/cdk/a11y'; diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts index b36a70e69..f857e6e53 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts @@ -1,6 +1,6 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, getDateFormatString, diff --git a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts index 274c3f994..afbe5af4e 100644 --- a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts +++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts @@ -1,7 +1,6 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { AccountWithPlatform } from '@ghostfolio/common/types'; import { diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index dcfcaf3f1..05a2c06c3 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -1,7 +1,7 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { DataService } from '@ghostfolio/client/services/data.service'; import { LookupItem } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { FocusMonitor } from '@angular/cdk/a11y'; import { diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts index b67cc1b80..75a96fc5c 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts @@ -1,10 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { getLocale } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, HoldingWithParents } from '@ghostfolio/common/interfaces'; +import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { animate, From e75be9d82a5831533fc0bb498730988a5dbb32a5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 15 Nov 2025 13:35:52 +0100 Subject: [PATCH 021/157] Bugfix/fix type error in CreateAccountWithBalancesDto (#5945) * Refactor import --- libs/common/src/lib/dtos/create-account-with-balances.dto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/src/lib/dtos/create-account-with-balances.dto.ts b/libs/common/src/lib/dtos/create-account-with-balances.dto.ts index 53740f0f9..2d1d3ed2a 100644 --- a/libs/common/src/lib/dtos/create-account-with-balances.dto.ts +++ b/libs/common/src/lib/dtos/create-account-with-balances.dto.ts @@ -1,8 +1,9 @@ -import { CreateAccountDto } from '@ghostfolio/common/dtos'; import { AccountBalance } from '@ghostfolio/common/interfaces'; import { IsArray, IsOptional } from 'class-validator'; +import { CreateAccountDto } from './create-account.dto'; + export class CreateAccountWithBalancesDto extends CreateAccountDto { @IsArray() @IsOptional() From 36b777081f4e19229b1d013ba9911d2375f36dd1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:08:29 +0100 Subject: [PATCH 022/157] Feature/add black weeks 2025 blog post (#5942) * Add Black Weeks 2025 blog post * Update changelog --- CHANGELOG.md | 1 + .../app/endpoints/sitemap/sitemap.service.ts | 4 + .../middlewares/html-template.middleware.ts | 4 + .../black-weeks-2025-page.component.ts | 18 ++ .../black-weeks-2025-page.html | 159 ++++++++++++++++++ apps/client/src/app/pages/blog/blog-page.html | 26 +++ .../src/app/pages/blog/blog-page.routes.ts | 9 + .../assets/images/blog/black-weeks-2025.jpg | Bin 0 -> 311715 bytes 8 files changed, 221 insertions(+) create mode 100644 apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts create mode 100644 apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html create mode 100644 apps/client/src/assets/images/blog/black-weeks-2025.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ae02bdd..8105a407e 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 ### Added - Introduced support for automatically gathering required exchange rates, exposed as an environment variable (`ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES`) +- Added a blog post: _Black Weeks 2025_ ### Changed diff --git a/apps/api/src/app/endpoints/sitemap/sitemap.service.ts b/apps/api/src/app/endpoints/sitemap/sitemap.service.ts index 359a29531..e7e05330f 100644 --- a/apps/api/src/app/endpoints/sitemap/sitemap.service.ts +++ b/apps/api/src/app/endpoints/sitemap/sitemap.service.ts @@ -116,6 +116,10 @@ export class SitemapService { { languageCode: 'en', routerLink: ['2025', '09', 'hacktoberfest-2025'] + }, + { + languageCode: 'en', + routerLink: ['2025', '11', 'black-weeks-2025'] } ] .map(({ languageCode, routerLink }) => { diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index 892b1ab5e..c958718f6 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -79,6 +79,10 @@ const locales = { '/en/blog/2025/09/hacktoberfest-2025': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2025.png', title: `Hacktoberfest 2025 - ${title}` + }, + '/en/blog/2025/11/black-weeks-2025': { + featureGraphicPath: 'assets/images/blog/black-weeks-2025.jpg', + title: `Black Weeks 2025 - ${title}` } }; 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 new file mode 100644 index 000000000..c5947abf4 --- /dev/null +++ b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts @@ -0,0 +1,18 @@ +import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; + +import { Component } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { RouterModule } from '@angular/router'; + +@Component({ + host: { class: 'page' }, + imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], + selector: 'gf-black-weeks-2025-page', + templateUrl: './black-weeks-2025-page.html' +}) +export class BlackWeeks2025PageComponent { + public routerLinkBlog = publicRoutes.blog.routerLink; + public routerLinkFeatures = publicRoutes.features.routerLink; + public routerLinkPricing = publicRoutes.pricing.routerLink; +} diff --git a/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html new file mode 100644 index 000000000..e10a64de7 --- /dev/null +++ b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html @@ -0,0 +1,159 @@ +
+
+
+
+
+

Black Weeks 2025

+
2025-11-15
+ Black Week 2025 Teaser +
+
+

+ Save 25% on the + Ghostfolio Premium + + + annual plan and get 3 extra months on top with our + exclusive Black Weeks offer. +

+
+
+

+ Ghostfolio + unifies your finances in one place and gives you a clear overview of + your portfolio across stocks, ETFs, cryptocurrencies or other + assets. Real time analytics and smart evaluations help you + understand your financial situation quickly and make confident + decisions. +

+
+
+

+ Grab this limited Black Weeks deal to optimize your financial + future. +

+

+ Get the offer +

+

+ More details are available on the + pricing page. +

+
+
+
    +
  • + 2025 +
  • +
  • + Black Friday +
  • +
  • + Black Weeks +
  • +
  • + Cryptocurrency +
  • +
  • + Deal +
  • +
  • + Discount +
  • +
  • + ETF +
  • +
  • + Finance +
  • +
  • + Fintech +
  • +
  • + Ghostfolio +
  • +
  • + Ghostfolio Premium +
  • +
  • + Investment +
  • +
  • + Open Source +
  • +
  • + OSS +
  • +
  • + Personal Finance +
  • +
  • + Portfolio +
  • +
  • + Portfolio Tracker +
  • +
  • + Pricing +
  • +
  • + Promotion +
  • +
  • + SaaS +
  • +
  • + Sale +
  • +
  • + Savings +
  • +
  • + Software +
  • +
  • + Stock +
  • +
  • + Subscription +
  • +
  • + Wealth +
  • +
  • + Wealth Management +
  • +
+
+ +
+
+
+
diff --git a/apps/client/src/app/pages/blog/blog-page.html b/apps/client/src/app/pages/blog/blog-page.html index 88b685d33..e84cb303d 100644 --- a/apps/client/src/app/pages/blog/blog-page.html +++ b/apps/client/src/app/pages/blog/blog-page.html @@ -8,6 +8,32 @@ finance + @if (hasPermissionForSubscription) { + + + + + + }
diff --git a/apps/client/src/app/pages/blog/blog-page.routes.ts b/apps/client/src/app/pages/blog/blog-page.routes.ts index 2b5a4be64..36d111c19 100644 --- a/apps/client/src/app/pages/blog/blog-page.routes.ts +++ b/apps/client/src/app/pages/blog/blog-page.routes.ts @@ -209,5 +209,14 @@ export const routes: Routes = [ './2025/09/hacktoberfest-2025/hacktoberfest-2025-page.component' ).then((c) => c.Hacktoberfest2025PageComponent), title: 'Hacktoberfest 2025' + }, + { + canActivate: [AuthGuard], + path: '2025/11/black-weeks-2025', + loadComponent: () => + import('./2025/11/black-weeks-2025/black-weeks-2025-page.component').then( + (c) => c.BlackWeeks2025PageComponent + ), + title: 'Black Weeks 2025' } ]; diff --git a/apps/client/src/assets/images/blog/black-weeks-2025.jpg b/apps/client/src/assets/images/blog/black-weeks-2025.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c71827c5ef5660d110c21275d432846f8d6cfe72 GIT binary patch literal 311715 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 >sP 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�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

K)NeQ*z)|$pk*+bz$ ze)#g|M$NhVhHhFRWG0{dbYad52UqaYK@T+Gu&43+w5^(T^^IRXkyX~EopP%{IBITf z6{DggK@kxvyWh(O4;6T9KQ+mR1K$9@8tCDWipxn}K*pO%$Z}Z=aNSV3O$}@{wNd_- za3S!B|F6l$zWDq;#41)LoLD+y;hUft1>)xOZ6g3e#qiu92&z} z1dky)wAGD6p{s4kgnCMnts_O@QH97zBdRiN$|OtM@%tXaoB@*%Kg)p#ENEY#_6iws zb{4ug0Xkaswz)gT=(S&`4EW|x{exSZCLTTKF=HnLnv-j1SPf8(!%hHVjUExcbQ{tN z6{0s5pXb!a@#Om4wa=XYd%dwz88Cfj9DzWU_}s!?N(^=Sd?NfpQDayHmcqF(+RLZf zElXM%OD<`qQ%j9avIxyv^Z~-lW`_ijrSMPE&UT-=n^Y3`YXs4s!J348p8cwt^LaeZ z1bZsyRr8iMuWcZzk6@a4YGj!-TPv2f*jAQf6bJsA&@x4;7MXJS7|uDUHgPQhNIcSu z-S&o6DZKRTQu?I`=G@6WIig=AP;$KE+izKCnnmCHQY97bdyZ}w^M`Yj-vIHSl8^-b z!N^&#M7lST{qzs_T=>qjQ*91*xlioc=QC7pF{_ybVBad2KT79`E2?BebpotYG1Bxa zG8s4{1-*mkH_${y*xa_F6W&qMwV_ue5IfZlTxs9~w4a^yyg;5frZ6G+D-Zdd_aO^s z0`S;MJ$Qx{3q6@=wKXfh6PVL?Ke#bHpy(*vA=Hr2?=L(AZS(rBDjgX-tE|UG$Z>0+ z5N2EzT`eLk_dZP@3Yu%x(ce`>*}zCkoZJ+{jFD1*!7c&F;`Ws*Xba7YGu;(-mhNcN z(&_wFD^D9+R{;OFMX=VlX~C(auWy`l8%$6aY=LEOx(aUDBmL%A3Xtv5b9l3T;J$mD z*x@KWo6SF?hZt~Ybgpa*1nfRs+1?{ZElyOp6ufYB#GYu^WMl=eNIccn4Wao&mC$A5 z+-GN;VQUwXBD_h!{w4IUVF2!2iY4v^H0hJrG}5lr-%=(I5uT%}sPiXNLKzEh7{eF4 zX*Tg#wti#?bPkLmuyr~y>(vqBx(0Y!(nZWscpsqIY9~Q<5?)l}&(K>|;vC7M{i>x4 zfMe;Wli=8$F4d_90MzC38=3DhOgRJde7^tsG9_quu(852x~g5wI+5k`nxl! zN;cQFyZ=)M)g zsx0gb*xSwVv*$3^C?~mJB|U$Px-PGa(bh}qNJrt0@O5XqSzxKHUM;62n2BM=$299n z`?{nJN~6E#C_)#b^i6hu%11YsUGChwZK-oW>ZN8~UgL;+Z<4E`Z#xso`*2^9J)Ox_ zPtZEx!?^(s&Cy^N;k1Og4GAN%Awu5Bk3N6Q$- zD`&v`XStWfB4g_o1Ncv-F+yC~@3aM<2ZCs(Org7h>E0%6$;P`!TJ-t(~ja8zg{dGL)lc9U%G^V!Vgxagwt)%L)F1gc1N>BMU_PoWoolt&04%r|~_B*D~5>iLHa zX3V=MKW4nOYOg6twUMg50=z>|@q;!j?EReFD3?jujuZ^<7~+3VIZovQo5_@JvW)t@ z1tPRD-eaWQ|1~TRZb}~;U9j2RzkYra`$A5&9%p81@s<=8xc89=kDkw%{{9R*H7pY0 z?Ri-6%l=P}L+YDBM|}O2%+s^Wzkd)PQk&Mh<;h^`W!_2h(w)OX`c#>REpe((ik~M5 zTq0@haZFZ$HwivfTc8q(p5uSLJnY=$2Y zd6I7vqS)XB#_p_%H+%gHDVnM0Cpwdoc5){;_jOio zp6{|8qNbOo@LBhVF$jZ$)QNRM-PL|0RM=(BXC5B@_$``#2{4cxA9t(|CpRawiijs= z(!b5QR2z}Q7Q1mSYL_G9{uI~J8pqmM&{)ebzP~-?#M`7u9V;A&HOk5L`k&MA6xDO)(&ws@--+G)@b=sL&p&G8$vBhXNkRJydMKS$#&P7qdDs__^1Hj5ey^jd>72l{hVOf&)vJPB z0GgU++wq%X4B30)g8M+F+k9$9XT9$$#o{8A_63cq{`Q7(<>no`8%gZ!ICTc;NYQdV zKM|Su8B-U(N; zcc4KYYS56N(}i5JaFTc-5&K5$}qvr9u3G}YUhQxNplybP`Gz^FW& zsXNQbPzzr$#@JL`lr|Db7S{eY_@%BceQC3WIvZFYPmvF2iTkW_3PKl!MhF*Ye5~tW?+{HjXHUSD&4_@49;3xhX-}g zzD8>O1klQENhGz>s{O*!ZXobS_6yv)`q%Bdll+PtZ_*Z+ygw9EuK)bUJU@g1XoQh>m`>0&bW+^6@?*c zvIuRL7xQcs`*($}n|ZH_CrrQDYK6Q=%6u=zgNK9jd=_Go^Yf5LU!2MC>*q#mQAwTf zg2g5HN$6c;>(_AmtC#RC4Q$xn#V!=h@72@fTRRS^kfX*w8N)(!Pf7_aiDgx#TrYLE z6^LZq%XYOqd1o;*8z-!)x|nFLz?a|02FqAKp6OyQg!)-D>Tksc?qoc4;ShB}*QS61 zLgOzFizE^vC0J8X09>9sa1^Cd_9hO$s&W0I@wW3-{0SPEurEgm^xk*JTRtF`YFwr z04W>SgYZR5ZzMIKo#2&t?qyj*!2E3VFUmQs_qRRu_J07AtV)e9Dy?D|Uf$PC3|!W; zWbaC-g9+5*>}_U?eyzV%G!g&9aJ~2HuiC%Of9k&sHv4N^m`8sgI2Z5}Nem0AnQ2dk zdeWkAwms^N67OYqmVC|e+?nCF>6#ko3qSC4={6i(X-|0V+HED9xw*K~$WB;$p0V68 z9}7Ilvv@a*zMdEbF2{G>9-H~bY517vnE#;m{~{x&`b+~feamp^%G5Vw<6PF|RNO@Av7kqOMTmgY7 z+UP&JG4TXbwW7?}Xz|(ciw1+`_rXYY*3M;y;p$9d^$I~WomL6_>Z1gGf{3;K=p){P zHdNlI&Z;zSKAtqO`&O%l56 ziA3fFF>VPMT4f&K!sLJ~#6JS+q@>EShBsus9;tw)i9s?+=oU66mkRNj6jAu!z0wO> zsUqosvxE5Gca1qmu8!(dX+em3ZT%4#ej?@ed*?DqOR}2eRSyFP3e35PuE98cQ8{G> zyzRf>WBB3{#zPcynK){A0Y^AeXwTi6D*WJ&AgsQH=aI$c_PgbKjMyk|7|LT%z zFm_nJoy_+-eeX5@9~wOB2unl!Fg@p8MZVe5e6+D9ICKsh&4xud$%mVa+GUDnlYZHc>=91YQMP!Vlp zZ7>yGS$u3#5*8}co@rJ=q;&kQ&1^RFG+ld@EPgK>tAHoFI;043qUkvEGRouss02Fb zj3g~x=yn7~H{7Tw-iccT-=)5nt(1@`mg(U zZAd*$(Ts1*RL_TuQU3+mP5Eo%alC>8MlQYi-f7X_v8s6SY4lXXn#!CO%7%C1Kef4f z^0*l6u>+Ob4#t~?+FE7)Lp!T4zECc8v^kG4rJ-{b+((MxP>Vk!1Uh_;$TcD2emhi| zk!%9T+RD~D!8w1j7k_VWyGWe(5wofWU6#V_wbZE@w0j#u7)MaPGtZO#f=vx)Q0ang zhMmpg2Jt`cGJXVu0Y~8O1Uc=F__TLOwBZ^}>+nf6PqP34K}x1@disTlIN&Q2=^MuX z(CQ!QLx5=$-k-ctVjz3-MB;k|h3_a0w%2IFEl+qHPH5p)SNpEAzQ7cOjO=Jtg;;)l zn+{&b`&pTfEnCi?P2{qv7h*)l-paFwCJD4b7=a8x1Iz@^SP{ca&42{ zu$c=**j!h7P=1{C-pnkDi5DMJ*j^U-*jO-3EQqwknW}W=1dWvqb05#Fpg6TdXsCid zMw~ha1IILnA>I&M4c-(h9>~HlLqAf!Ap;$*UsjMo)HRDnU0y0t@)3&@b*HElz%)!{ zb~Tu5hC>UPf?w032qOmaLwt=#wYs*M44`8fE=dGmRFs!8z^{rYTA~asZq^|oM8bWcvXisY8-r5NL%o=#INbp90v>6lkb_4blL*4wqpssU0R zgDTx=(-g3bqu;b>8KnPLbuX)y@mLopX?b-!Q$;?@(~Ps-@E;KsU*0)NHn{m*9#~6* zB)SM3pDN_hE01=E0RgNm&P~adFS?t);H=P6_39Nkc>2<-I5iU7n>Q|ML6#(r5$xOa z;;VB-Q}S?$kickl6P1jH*G}F-`7kk-L&9AmImGpVOkiwXSW}(I+0pC;0zuonqEF+g z-nj7t3pZum?p9AI;%{QwR=p3at8bz}l89k3Lyb{xGqO0O7HraDa6+t5zaL!nhj*_K zxV8d!1l!~-YRA{>&Ds7CU9STTFa!ukw$#6^6PO+@N?PTTVBiW`5Dx!yZm4&?RCbiU zRE9`yWBq#2IgLW^sQaC{jX*2-!&_gYdYHVp@FNkMZqi~j+jIl7u5oI51Y=x7ZTzu? zF*`-GMa*64{gtT?{THH>qy`ixB8!gq2zF4L)(0}bFiur_ov-ePqU;5Q>;zRG%~n=9 zn+mwwPU{g5bk>{#h87NJlghx>81@ls{Z}R?YsKmbmU0IUGR|v+P9xg)S#&SIs?V9E zM#fevJJ8nfx&RJc!Ty=eomB^)acng0N7pJQOVLeL2(|rdnmVVCC92ItEc)5Y4Ns$# z%^89a9N1A>aw_TR4dqK-k%p3tr7IX~1yBJ^om)43Zmbn}hTb27*Ab#sG>!1aHxq{C zp?6z}Io#xycf(<)@b=QG-!&%lhUzu%s;dp1e%G7~Im%dP2t)3sPqli!@D^ z%=A%W2sIYabikCMjIyKYcYDiRN^bp5PHsDdMlWf87t!rjz3DqVlB{njBf-TCoq~ck zcMB6}QOuI50hBeax5w0=zC>FIgQ|kB>ajPD;q-&AFhsO2)nBU3>25q`AD!zTK*U?M zQpY_bFgmVuc@E`qfb+DP5|$m+Zu_d%YQx;TGYT1yifqEn{s|^+sfFy@iY()WgG4fC z+zkhXyAjjmpr|NGc<-qyJMTO(>- zjg#kR_@4e_YjuN$^|s?@r;+e33ENIz`9MIS)ol4;TXDefJ0<}5ZBFej zg;BpP3TNL~$bh;zXq#vg$q|GXYi&noL0@p589wyE zKMTJ54{fj_N1H^=z00m(!%FdA$w)Py;M*s#wZzua^t$?!;HX;q0-a2(bl7=*+rDR6 z6M!Xw2F;Zd;Px}TqjDq^*b$wHXO^la^+>_cw+_yD%$bOFQ7?ZAKl-aI3uYCdN*lDA zsDEdbh|-ExdAL4A?q%VY!`bF11#L<5zcDWoyM)RNY6u5sWJ2ZeFh*~Pefk7?)!ZtU z95`8y990%M#&JdgEIxfgkd{e9RtAH=aR13q1ExEf?QJGOq@C7KZ9&z7&0vEcfd}+| zLxMWII^kIMF$}Fk-2YJQ%P<$l$3~O1y~dRiqd6bb#0%^n9Ak+LBO8M5nXrLhrTE@NZ6f|MFEo zx065UUTSoJg(exBUlMu-3FMUzG zsQY1l+L4r#wWhI3A}Nlf)ZXDh-S#Pa4^FLw%*ux*gEQ|K_LD@x7$=;z&9J>OYnW~F zKXK~U8>9&oejB#7#F;f(u0c0>wr4sMAKtzsdKK3d_gdM_({jl_E*|fuDY>pH;9@Q7 z<`A}o^uF#VXdXf^H>9)ciPs)2Wrb<9)cRBZVrFp^1G2mf=($ZIt|I~$&dB$I>m`7y5! zuq?l}j(VNfYtXH_b=Lm-2xcVi4UTE5%2LAy@6@#0au(H=-dV=MBlcU+rSn*h2yF{$ z&}(t`hW$?7GqoWNpua16d-INW4yAe3Boz2cH+NL5IOc_+pJXt$UBupDXmOA^s;tHkTZ0*v_0vd<9_hWl8y9? zj64N|Q6aGKP+l0{``lzPLhl(WP4FVu0Qe=$_~ff5{7YK5)?dBzn;ZH$RUa=L8Jq5) z5B)Gf-?L|lTSJTTpiC{*ZwR=wS-%PDq0c1D16h5*vgTGFl!w5^g<;7PZ zVZ-7HZGqPOCLA!I+!t-l5tCtYmG0e(W@00BzXP5^bF+uGhyji%Ji4`KwP!JAYUn9t zO!IfYYwbe(`Nd%bj;z)*-Xtzk^cC3qBGn;-lnW}4?r>=#^wWfu$a$&4wJK%$1fI}C zs7dM-HKr3om{OweOz=Tg?IR)QFvZ`ina9z#gyW!2$b}uDGg3hF%og}U)qUW1b^Ty; zT%&#LCWX_}hHddB3ana(?njczswkaIKp!DY`Oos}yR{ngF$(_i1m2cm5eK!;z@5a* z(*^tH8r9&>8CkLFP4d1ot__X-5X`%8=g-{Yo5COnp73oXS8YU!SFe+1t&Le+e&R*B zp$f%`&4GQh$G*oWRewS%p}JJJ!V6>dxwE~B`{h5JYJ7awSz@k06%k~w`LQ+5QsY6& zPDI-1_aD=r&)8dCG(99xJ9-=nJHxW})2n~2t1V3wc_OXSN|$42Rff8k$Yfl#*mZ1J z>n1O4znLd~oIVJuL7IX9f|LN9a@Q$LeXdh(?DX=yW41y%8s8}R9zdaiW~SF}JfL=p zTb2L%)vqcjS}=6;Ypc1Ck_gs$@?xs%7*>OqiTK=h3Ky)2LCQ^QF2a8bq)GYL^$6_0an(rdDvx{=jLj-TF+VQJd(}AXf(5e*F zM=p&QKY4m8LsT#qt7JX7PR2usVEQRgqdYqd12kl)=}5?fj< z)>};cXp4_@x>GO#ZEALg(={=S&Qk?mqUE&YyN|ZM)xwUhq`jOp*fDOTMAhHsivE-# zLv{0q7Ug74g$pTmDk5NbzN5rM9zP)_m;a#&JqawkE{l81wj%$LV5{av(x$0x7u5-z zo$T(NT6;^zi)$u?04p~0q36i23w!OnUx_`hC;Zyvxrthvsc<@LLS^-DrAf-XCSUP# zT5EhzKXcWy>0m|km$XU1KD=E>G^)g{2xX@%1P0HE;q2g+IDzgmhTCY~Xd5X?}(pX4y;Z+KWCtuny`8 z&{DpXkKh+LSn?((1(oUx1CkwhJ^}zoGmne0yX2Od)x_NuO4D^#3Z$J3!H}`$sSM_J zzp#IsTA!p=mt1Tr&1_&zjZ9e&{nk>vYOE*88;W$=05#ko>qLsQ?o=;hexB=Z*WU?$ zW8fYJ@DZeqg9xFmsf5nk z(0}hsNIXK>=!Cywsml6yf+IK4tnX}f_}^lZgCy(ES6D?*%_>{EkJI3p6;z_M)Y=_V z9+5jBPXnY>u$X}nS;{(y`kC=^zq{Hp*^{iti&7yP1#%KCi1H7}48Oe67_d%>ug@U0 z+^Ql~ty3qBj@)ObWEyDw=D#4XV4Lg|3qZl~0yf-qThtFeAtL59%SODYnE}4j*v~!S z$JKF|*P%||qp2jP>(ZH7RS=BPQkE5$cIEf_d2%M9UXEe*hd8Plb;00xoV$iwU9%0< zAgN|}?NXnkwu{ns=whcBnJO?MHgm!IpntvZgO_QYHXh0YC(CDL#_g1? z)cDbe?9HqTH zXwhvJ!kXxcAvnGC*-KP5_r3ZWu^zu6UH{tMG@>7!Hrn4c#N+QGfyR9YIZp$2r)zcS zD}VR?P>fdXYM{;o%bs4=QUIZ(-`~Y)Cg-K(Z=jlQ&X%gxb1H{Dw!doJr$6a^YKDBb z7kymSbQ9uhVNj8>ZY%2U42jaLuuD?(z7aLw=Y@`R8N0Eksdv**3y$LgkL#Uo73p2C zM*y92J%c9}U8o}3%56c{=Zbq~{L&iOU zhkx>DLHq}E@e7pdMwk`TI6Dj3w)3)bnSxu3)?T-9a4(2}^RQ{u-zq7-_T;i|$1ia@ zS*3U!ve~>>87@HK>4P=sqeI&ySVA7zCYzZS=7qH(I#-Q;M^6$#q7!=bv3$>^e1F7& zOd93U(%^dfBOc-7yj(bBi`Cs1I&^@mDeZe?^feroHrVjLj!4i0}Fz|_bMw#LWIwadZ1ggb%BJz z7#Z!EPG)$=fueie%o09KuXIK|D#XNc%hEeXZw|!Z=dl9CU}(DF209+(o2L58F2J># zB7IBb%bNLoDu-RvF7^_P`5IVci`%FfZl9g=KJk8&BvmnXL9vnl;$yHd+e~&GoaY;J ze}5%zkVlGI1l%U_p(asaFxC0F_MUQm;6?B=cla48aGplHMK;O+fHR}Ly;Z%jKA5W1 zv@T@!rbh6s3B&DLOnQZnj%^lFx|nfQ$qJC=RYxnIxSJeymVZ$*AzB2FG7Eo?p&{fv z?Xz9yr-ebFW{M+#K8L#?`whi1B$sT;bkZ(nrgL@;&+ltB6-$QX=j9F6I|y*A+q|#T zK&vdOX)>Wc#!1%SM}CU#c+sVCNl$Zp97iV0DE=s0_!s4)dy`uajLfa$JsyRr z8bqM2|E}E#)n2hbW&Ha@Z#UHQ3Rl8&tiUqIn4bUrhravIgFF>Yi6hc-8~l7M!xis8 zSZ;A6IvSVljdG}@cPKI6C+Gfp-E7-}$~ii$a$1sY_37K?J4SEKZxVi3?a8VNP%OuMrsrl8FMGN-4=VT^LEp3x+t>w|b)QOJ}WVCFLriL&IU4f4XCSD41 zD;d^0EylHlST`M(&Y5JC6&lEHS-Ha3m985(PR`E_F1Ifr06|6ClJ()yQv9{x#5vZM z$+o8@NZT|<^i9Eo%)bS@x@RI^yffi+6g!jdm79jm{xvcQMaz5;dZZWLi$+3Us-w5) z$y4xfNa+S6_3Lm~)HxS)jn~kJBtOe{JYA?&3KM4>R&zIOikB3Eg7xDY(TCU^D;B6g z{!XKEQ>yAML}XcIRqN$kPpzVmxT9Q@eu|T>j+YWM36;V9zv2Zddi(#9I8TWRWF4-} zu+zj+4l$=9I86m)SswrB%rp3QXxqXiYhbe{aR80Ul~*Y`~>bi!J@w zwTTsAy8%N%Uj(drzEv;x9Xr4oF}p-Oq{i?T3}TlgGAu#Z*KwR71{0|KiVn(+EfMH}yq5~|)|f+%;8 zRjbMXOXOhGsq^Nl=s5SZ&5e3iM=y(5y(bo4T+o)VFD0F)Td-?zzTfOTI@LOh{{CHa zzg+Lcx4-(eJb^fosp#lzr73jUcRt~z`iEf^^F!|t+NOB}yiN^u;bQtBAR#TncQR?| zGyNClho7aMTw+dap@LL^Qt*%CMANKNhE}MGWCW$1TifVEjsDMlHLZV{6z>ZAX*_5P zqCbc*arV<{FVnWKqrC!GL06+B{ZuWmdkA)qF()$TG*p>s(#1V!99#qW+T?esBM(R=b2w01Xb)EG`fIM+P+}M&#BUiTTzzCgJ1l3I~mk zhTKdd-b|q7QpT6?PP!2%`kIa`WrLK*o`n)v)sI$Dw>=PL+F%-}9uIi1f2!5l2^N|( zMyP#+kuEMbGIGJ+ZCC)B``q%Qx>3Kh)w}JjX?-jn60p}O5*=6i*)JJMNP7sQOB{FS zzyHwY!s%8qh6pR@T>S0y|4c|m2o$#AtQGnU*OhAI7TJHijW+vwa-LY&D3T`g$!r*? zlBJe$XoI3mt}_JBUle5;W5($3DX>{u|0oaWe_ZS=7-FRNSZgke3bf0*X3cdkQ`Q_c zpiOIrsYb}v@iDy^;VqL#$KwcG$aB&E@SF}_yT7!>V`sZLmy=$}COZ+oOA!ZD_UgD; zvxbyoE9c(RJIV{cp({B6Dzr15&j@_Ifqp6Ca@*v%wi3G+(%yfRNR z6oxw(S;B56q?V<(lJdK{IH1500F!mq=^ozZM8PzsNY&nw&Hxj?Gw7R{tNn*||F+y< z$|xB^stNRxLNBHnE-vjW#QyB%MOnVlI59|d!91E^#gq~8RNT$DNchU%YkT?|Z*#m~ z>ricFmeB)f#z*Czjo;5H=;H60`_D1vprZDIR{ zi@(hpM}1F=TOBK-=5v0Usnjy=Xq?{dP|`e`*~o+}uns1(b^nRCVvPi4->Ce{f8E*~ zGra!q$^U9#(69AanB~ti;m1lo3QWqYTwHuNIf zHxy5OMpdK{qI4*=j^L3AYPa`zy~C<5{qQI}-(-D>UpsWsLs47D#r^LMZ*OVkpts=$ zZ#Nf@RZG-MIhmfn_MOwLUcx>!R_%x1fCQDtDRc*9VJPuWS$6!I@^lcD8rAd$mI*pN z5`-Dt4)~&8&HDr9J-c-lPyD_$$5v^x>*;ftBt#$PP#jHBa)J20{b41W>IWb@FNw0| zjeu{&wEXr3_~UtyZ_J@zl$-@1Q_g`!8(V7s%UbDIHcoe?W<0`c^lSV-9A5cvmnp}u z399(sy`hKto~6~Tz;HARosTm*bsXZp-PBEW1b$4y@Ih}DXh_{vF{3%@#g+J%`Pqvr zr#$z4yS9&$8;?BKhlzC?J+mz6G>iKWKpW<6I|Oo`vI17M?T&w2sUICtiZ$QI{fhUc zJ~b@e1x1tw!Gs%CJ@853DDQ_w5^G|cN7cp%fi^=hWV)VR1Zlm;mIWIdddO1bF+GmG+$S;8$_ zONIG+yg`Qt8BZN&r4L_kr$jY`v6P$uEV?y2zhzt`C9{;M(1@7|AG_Y779V=scA?!agkHAYD2Y+ z4rmHHf%0Iq+=Lk3EG$|2cY)TcRhddlSja-Od`@%LnSd=$bpgEcvKGv00C_Dbj{tBY zlsiKVvFQ{4ltUK03=(`f5tmlA{v2Hf<8C^zV2Mlv4O>ayHm9@o*lQgC04#j^A?L2B z8{nid2KN@Nco74}afZoOd~B-+sl#d5ha|kEWDtJ1X(e0Nj749v(QXjLx zPExFM4X4>-`nj4|hK*AtLu9QzBn0L0VBGw*RxWE|QlM$sPWS07H~=f{^3M$ahO&E+ zZK8kuZOn$S9*(v}1@*@a!luZ+wEL;R6pTHjY5;-DGx)-s_G(87>xKGgOskYLSmjwR zi27}BaGvx-O61)L@wIUKKdDCs5|18610ZqF?qp&O0Za|T z9fVj@DoEyWk(*$BR6JO-*;35`@E_X527r=Sr62S|kgL>uJ;9xglQLayV^fXV-&!&v zB#ax`k`fzuC}zpTR4{X;{7)p9#|}y}*qCMEhMAIj+<=A__V}lE!t(97FMQ2l0;2a? zlJY*_3?%-W5^|Nmx_}*6Ukck>jm9ev(^UM+L>vBj(F{{>lB~FknaW5fOH77V2SEL+ zc4PCze}8wM-cRI<75L?RkB=XI?(~S^)xad zxIpclo28UNadS5`03Nt*9-PybIQ&B%GjczC(S4lkI?V$xB1KQbCIt)Tm|l_k_q}ED z((|oRQi$Ypc;D`)L4dtuWd{quO`p@;;iMm5DpB|tZ45{$wY&CzLf5Cj{M`7^DK+?Hig_MOHSqKVUf(vBf1GHQ(=q2Z6$>pB=}N#fv`QQ}SP^o4z?y{cnX!$1caeJQ*^ zm|B3dTcm0xJ}Ac1H~?q6T!huQzKjLNNvbd~wW;B+QXdevk>JV>U)^+fv&B}Vk!)Ib z2412K3$2SFslf+H?)tc8hY56QWIp}dN87=f+=64D7A3>(x#}8vSuRTgAX2zyL^L-yq2ROsVsq=hpc+x9*T^ZmHXvkfa!BN{HD4!3gSBcWFGv zZ!45-KeZg&tK3u9wEw{02WO{k+P9gL^G4lRf2B>KAv>+^K%!OtRK+^ORJ7@I^Hp*I z@ci|Ma^6#u!UOt>+^QPddd;)+s>+m0w{#T{!=ARpC*|WWApUnec^}!jX+J4{qLA~# z2~A^2l2VS|=9p-(ryEnS!gx&i)eH=7;O>2yDSLF>p2^GAK^gb4Y;Ob zKcM%MPJQx5!Y!RNDc+y{InMi>pP&7yH8*ADVRIj&_wm!~=Iq_&{Y2LSN#Y43syZ@` zwzKjht5urD^9e}GtblRTyZd_r`aPZeiuQ>kI788psI8?k`SxTv?dx5LIr` z>K#e}#SCe+!|g9Ho$Rl07E0lpHiF~6Or+HN{ymypLj_|-#E-ojOKVzfJ=284xH#mZ z2GuPgoFNUE-00toaZ~W%ziBH*8i0dMoO}g4`vO$?IGi5u>Y)Pkz|@I4#6q?ptVa=ZvcLAc(}|T{6Tp>@vU{jq-9$D&$rg*M%87z zu^zqkz1t|VUnV52pM~7FB#-4P;7R#hd%y#7HvFqe{OOyOV$kH;sj>0KCsiJr;ZkOt zEM~dBwfF6S?n)!06)#D6+}sNCg&BXKZYb9&ls!w$-Ty?J4azGu99qqvuqZTn??^Y4 z6mQ+p#j>7ou?W!y9wzmP-e6hn*Q=%u42;M7{R+Il*AdNekC6)s_e$|*jx=dxDlRtC zT(!IR%zdxN0oJzb$nUJwB0j|THSjxu`xm--aQ@~MdeG4l`>V!(057;s1YtDUJf5XvwWu@A(*-Gj<%^2#)6dQT23KJs8(Gq z_Us$Rg%E{>_vw|Ns%cDMR1>;>@VL^#0l^X@e`1E)Nnc~eK)x$uy=L;ZF>B&~mwg0@ zE9YF*71*8kj!cv)$t1h%B+CrF7oBIBPYq|G zq^mJVPI7GE#d=VN!I{2V+xU9Yk2U;@Y5H_4v4ro6OKvHj2fbv6r0Wm2K26`;)mv2c zDQbw~04FunrgHt!48OF4Zly7NE`q`O2j3a;oHjEn+a|$X`+BJfrU{e<= zbzdCQl_&;OuAB0q74huZFaeP*VSZB$*MskurtoxR;9f@!zVfi{*m2JAkShSD=pZ5YjiiuiydFakPeOywLi=g+&&B(NB>2ac{32B zH{d<9N&_0L5(6e*0h9Bg4IWjd8v-01D;VZEQ3{;7q5-T?nHH%xmfbDsFZu?0ZteeMLJ@kZ6sRf$4|vG};B9&B|0Mc>vrR&uAAe3w5kymH3pp#sWO$LU zIYqS?UDVLnJg-|q9Tv23<||j&8<{!dLtJ4mRi~`_wDx;bG~J<<19Ug5irP~7-BqUM zeungr-<#1usmepDKrc!Q7&*p3n&<;)c>@mSvHAtvPL~O|i^B3~b(dcP!E3K4c^T7?0FP?Wl z3!e)lQG6*KcDHomqUXNP%kG1Y>L~^e&3LWh&FbAOR=33?>-!8{76B>YxuSmlz}nD z4}Sq$C0^s|aeA%(*rH45%NA^_iyV6N%WdtdzT=@yQ{=CU%cUul&qrPO z3FAwdl>LWEgYOOg@cK4mIroY@l%~XfU*9>`qU?qMqcPvt>pCx3yoZIyDqgjA{s52I ztFs3kWnk3({hHvssB2J<>U=Olba`rO^P}+9n1O^DV*RY;Im315SLrq#Cw4V8=EiIf z1cXL)o<{TC7;HBCi)WrrN=;f8s}(OR342z28G5@iQR7cur}&U-TjvWuJ`Fx1as@yA zsQ%?76i#1|2UC?-+9mF2VMRis!a;7A4%a<^^;;9HQ@^D}Y~V@9;PG6bWoaO?db`kd zTjI_996?q!=AB&N8RvclVA_UAyH%y##AZjwb?o;>z+CP@=KM+M87C;0em)s_KM!X+ zo^we5lWVo>%YjcXxaZ$F3iMz&)=$H^x#eU@!`tW9%tUDx`g*lsxoc2 z&>OnGgclwUKC>GCkoi`3!zs;nGcP-5q~uUpG-LO;f1bC8918ebU=*YE#(LZse|7G6 zR-p0RH!)&hy0hcSJ}i)ex4?rvYj-!;R+G8yx+U^x+*ADPhHRO0calYSmMt&r=%dHi z=g_k^2$%OvIlAWOiIBbgPstLa6Mh!TniMCcmj=a%_05z^CES+mL7N#7@Jf#Jj#1}) ziaO|SY2?E{1KsE1XFlA<@9zkvMbRs$UOa>y8-%wgl}wd^p5=*lsnwyEX_u_ZM!R5~soKg}j+M?Q0IRI^8w$2>PD2C6nrS2(~x;YyVSg zX}cOB8Ol%Jca~wiM|xYI?S)$sWs#5fq{y_^qz9infx;P}B=5ySJFA6wJA3^6TT5XR zJm-m$e|Dw+>u9I+e*lDi)xam(Z>HQ-;GkxsGDR%2Fq55WUV{tabeH8ZQj?{lAD`=U zH9&p@uF!q~S_6TVUzy1Jjeb;zFtM5*AJaZvuNEw~0Zxwlg^sCm8%tFS*N5ljz89t53{M<#okl+NJN`WSsZHxo7&;vtcP z>vOZC4NVGT?c+(+3{l3utk?YBBq-*nF-am_E)_d9*l2aU=Y4gbs$i|eG?1}@WxbF& zJy-IsytIw9QLO_KEdR^2`-Aq5K3N1_sxpNuEu~V_`o8}?e&Y&!7$#IKzymO+Dy&gw zq*IfffFQ&7j>*blmet5_nsAa1 z3WRmlEzylrOw|o4V=sqUGQQRQWe2D{8n8y@E}FI}x>i0%HM7~VIKJatw$QH~Gb`4p zZC%1uest=xvm`p!(-2Vz1>A2^*VyB0UGo=HH0$hCNN(U-26Ed(Wpvra*~^0PRXJU4 z_U|}s*iAU`8Otk;>X4UldJhl93Gq@|CKl+NGT0L2i>P*KnwlGK(8plIf`tl52kmg>6R+yQx5--9CI%x#~)YasGd zk=-Qt7v9PnmEuuifO89(;uPL{&lWP4PfLeb%rr)CdK zA5z*8QVui~9ABOdzfZGC+GSfC8w~(7p7p7Ic&>eUpr8-;iO3}Uc%GJeL+f%sn(y6b z;)FHP2E90+ppDzyR`p90e`h~MN~MDAQPjb1?0<)W@2IUruqDQXI#2>KjEp7z#e%nTrfS5eP9S75_WIQF;l z_!AmoexKGG-(dR%T{Go$#@zdx6?Lsmg^2c~bMnu`=cB%8P9+9QJZw(tiHXMYqu%!XSzN zm7eE~ArSoKp}aUV4hzV{e(NXIbBm6Aqw1WeV!wXP%5|D|GgNJ_Z`x`fzoDV4LmuB3 z9GAF0MXjWAQiiR3s{ncNL{x1sX#TXjPAAf{VU1>=5$%#6+6GKG8_SKLz$v@xHN ze(hSiCa1vU6r~-{=I}hauR&9jrHzlx;Cb`*SXJw)Ae~c+M83RUVp0+_K>jCie$C zUAR&TUiN)j)XbQky2qlEz2h4?uQ@zXt{?H69#=-XVzjI?$G?qbfFJgLuOL^LX@XQa}|j+ z*>~(pOx>N~E{A#8mi3MM(GG;2357&e%Ta2KVbEuGoC80~az~ zDj#2ruVlWa&$9|5;hw+O{qVZTw~B(tgi&M8pD~aoj4OnB!i3Psp*BQemaiq=W_&+V z_UU@R(avP*4tKU>C57GN=0Vn%B5N8Rd})j4y(|-}>kSfz909E%<=#TcB!v5f?4pQ+ z;dle7vt+f+y{GkaFx85b!cLGvxgqlq$$(w!1iu4~j^IYrpt1_qzvuU8D6jEY9dWgr z7dy3iT4}wfC5wg5UDszLJ~JpfrEE?PfqWK<$TL)1^RD9a>6BNx)#URdghk#cb+~>3 zFu43W>O>Q4b-HMD#oR46AryG3qVk<}o05AFj5bnDJDzLIwuN}JRXgWf9DA5E7Pz=L za80T8I;6enl1vU@LH1A#fxNj*&uaVTh@5Tp*Eu!8Oy}5#?~bg^zh{vC^h|uLm|X{( zK+Q1-DF315d*hD3>HY*ex2T#wZPY~W1Z7Ip_DYc+pwmB8y|Vp~zz;kE{R5CT&uzlT zNm zRF;lK>V#3`jUZjg$-}5R>X$DPZQMLP_@?AWUp8B`ecx`wWZJg=z}WbzGU%Z#TG}>F zZ3@I#w_*3xgzpH|h4=CX&~|&u?1q{MB4q6>9p~cjSNpD4qBIKIZEQOEPIq(yInQ6A zL-apKohWz&PWcyM@YXPsfU0gWu6rVr*>uTNl}X2=Kfg7;Ee$T9gMNHmshNkPH9E8E zc*EXeZt9|y7R@6-2-Sf>Aiu60*4y@bj7LljXJXSFY@Rp4x5hO_24+phNbX3qUIzlb zXbHD(eJXu!kONwj{c-7Sm7kuJ@bG8lLNC!fpZDNufvVexA00QD|At6_<*KWYtN^wJ zO)y%%0^qNM%|_6PVWo|YRMn2rW_&;(>0#^&0_Ys8rWKFSHkK+q0vig>=+%m9Q&Wy{ z%VT_U*CBK!9zSYK@2L_fZvQq)5+nZq=B)qa)xtc<%{8RK9O>3R&}OO}JS>4I8dkWL znuGb|3~wnB8Jo3L@8&%DQJXipX6hv|Kw+J2A6B=|b;VqBA|)JK3HNHWB9{pw|X|MM`~HBak!ARVv&JkPNEpt-=Pa9#TE*} zF(?i!65-7x1CEj^!7=L1iGbvgOa233#RbjlTZwk{>K`3oP&8R0jEHwYPS+4G5-L@U zDrSGpS5wGThf)jS5G)$pMjqy?A=D1b_{KE#!IPEJ&W9?)C`CLx^jm!hFFdLJXK^sOmi*@kO@zP<-GgwunN*~%VTX%X(@vB=q1 z&(%RfO2W(27)X{4SX*`>=o_jV9C}vl7uN3_*UxtJ{Vbo&+4}HN*THnfj|QAO_8lvu zYss0E=2%`y6nFcmkJ6`u{2I;-BGy21G~@$;uf}|JsDI)3rLUwuIzMDpEN%&DgH=g3 z{$#4Z-JzaMP$>gJ6@z{%7ecf54AmrNbN>iUYs!nSFD& zfD??2JH1F`0|1h zDb}_U&2^IUzV83=nD1xk)m-hW>>CwZ&F4Yb8O4|0tk~x=c5eq7P9EJ?A22`6YKZRA z5_U6JA|M%jV8b%u2)KY0gA5im6WkE;g(T9IiCbkd3c47wkBW7`$7m~lnXJ|r+kDf=a2ED z;JTgNQ>E*_&Bd$05~W4R0%?FuaNCFQ`fnHYk2XJa4CTa+JN)i!2YPis04==gWB2%^ zXr|au@9K4zMSvfZ8j~T6|AzIVQlvkuY+s1e>t`>P+*2&Y&gxWBs;P%DL1pe(rA^u* z5}v^FcMADGTfP7N5pLznsVd>rJ5DGyEJJ>>2mqJ834mmb_3VJP&&=c0pqJvdvBq^i zDI0qV*zT@pS^k;i))59Vm73)0SCxb$=9!Q*wZE<@Q!@`P?~|{0$@$(dNO~&r0-iI* z)=>RS!GdG((ketIV>*l{h=$qgBMHaNGX7rW%mqQkmJT)h-sHHB-2y1gpZ@*I6TAhHN5Ey{Dep;9l+~m4 zF0XamHz(5H<~k|>L9tSs@8sb}=~l{&O1iHzhVCAK-@pBwKcOD_+Wk(=V7*G0C)Yi0VfPnO4^STs@Rxt_dx6=D?NOT)nR`dl5_Tl5-%eyzQWg{1tFuNI~{jJ zjAReFO?aKtlGq-81Ws+71A1?Fdn-xAiims);$r(q{zt*$9^p+8irS)8>5F`}jMY9P zwMzq#r(=;?DQYhSfPHy*pkQY2bCReri&YJ5H++C4|3PuK;3Kx@obNVU7zg4hLxJ>D z0sZfchBH4NxR4Ar32*#*A}Wd*D7hf}=UN@Dd_vw7i&mHS)?4*6O?{fHDV4tX>A7$X8n6`e=0!dqwH%(|d0T1#KAyJm zBkw^VaMu(bEluX)OfEu=R8rB$p}ZiOzru0pKe+wbb0)kD1gCG}_TA)9-V4AtGE(%I zMuo|Fj(Ci)s>cj+#u7zZ(Sk3rhN!+?<>{rqtL@#QB57%>+r) zt9))&vs=eNVIRfAH%V1`Z1jQk(C1m_5{!2~Nswjruc;e%Qp6s!{NLQ=Vu^dv z-mN`3MwUsIG_|V#03ywvXwFQ;BJaL0KO3I0kTu@g)W($sFHJJjzXZ_geq$n?xr5Kr z2v~%~X-Na&zsj!5d?tHcR2Edqkl9pn%Ilf7j~fTa+psdGg7cM@eO5;kROzDugK+|> zoR+Ibq?2WNEGIpv#ex^(-Z3@_Hl;*#qvRrCN89ZO`MW!IKM|~^auANC0F^p{TrOJ= z%d7piS7%g&j$f##RRbc?+nXQ>B~O0^WSgAE_y*?| zL4(hmE;gQOF=7@7pL$lNlgu)ldHZB=e*0ZR>m`q~!fe8wuHdKgRW2^1UAlS3(oZvq z@ZF(6Nr}gzuQHZMYU4a;57A875Q+MI?<6IsCJm8x&1~Wz-pa@@qczjaymtA$>@8Bb zMdhtvnyy5p2Ks>ag_5NY)-nCP9wxVAlM@wDGo|rCilNP6(Cb!5&p(I)St>~ht}mWq z&taEWO)SIMQvMDXg$`Ss@@GL&MD=M7tOpU;Os2RbVZ|{i@h_dqu$6##Akx0WN74j1 z&UZGfNFuA6tik6tsUg61V*gg}Oux=ek4qXF$AQhY=OT3Qg`WZ|(hnP7xsP% zGp;hp^$y22VB9gg0W&l`cj1$~^qFkr+Z`-}qOWP;@XZ9Ta>Mh--(lcET~k@-VHy#Q zJ_qP!M}(;r(9FB|qRQP=(f+!A5wb?y@Yqk4DVp5$#9UL-n`C^EOKK^YUn-|dPpV|@ zjh=bX(n{lRB)WM&_Hw1fMI$wqMOxz!=XXaFCwU*Ee!)l~`k^d$TfHP^P+QU?Kj@4IBGv3j=@t!fqx^={ip)h{5t zg|4J%9iL^vwh-k!{qIMNCPWzBT|E|IDie+o7We9w8zMe-t5;5nwvBmEf?vw@A|W$0 zrt^D={wH7PqZh_6pc2#1x-wZJ@54_Y?cpxDJYM-mx z3?84^8BqRqqf&axa!(rXap)$%sTaIAXKi))+ZSz%h0ENOVL}z>r0C{3Fnvhqkn>wP z#*((r1RjC6&A=!f$Uv(58~(i&Q_c-1p|M2^(N|-lK#p>ifz+~jS|-c zKcAi$eHAkJ_SQ{2awRSI+xL~W8K<|qGUtT2c^R{|-|rOL?yvV!ee1Gf`Etg|yyrN% zzn_!#jb#E}@bx463xo#X-l|-x<+vk&Vt?wi^s>vBz5k+PdS*~wePv<~-BiB1^(5r2 z?#?UKrUz9nMF+2WFU$>e!G%9lE|H%@rK54*n7_zRH{2f~6c(K9whD8O@4q>-J!s)f zKV7pr7vv+YC~dCb1yPeTzs0s}_{$a~pQP`6IaJ8n=`+H#YJ#S?yvwCmS?xQatEp1 zX!CynX}^9JR6P*X_bWon(@Z4_K_{R3x6LbKvvxhA7#=A*wvpC3#U2t3&{1eLE@pq^i&Q=EDLx-1vGzWRwA zZT|pDg>wYxXE0x)?vs29EnfFud9d|yIn?Hzz}??4&!9t4l4aQzN`>j7;s<#%t=(_b z@NWSR+1=-(;_ED(MO5Ky&ft@rD{P5ds(R=|nY*LSvwbe+RX-WPs@`vf{EgVZVmsrG zx{|%r7HyqxvM40!0Yz|q8$%=j{PK0CmscH z=1^Aur8aW-sjxUZ6YNl#;Ks=yfUP!S>%jFpT6 z!IW#EloK7vuL8<9H2NCCNq|xQP!fJ1Uz#9HK}|)c)B5=q%!s$gwItd2b@sF~`SMl6 zx3*;1uT*9TPd$;pIhSy#e-R2+SWBR0hipg1>#Q2bEb4 zn)_EEKT!u}tjD0(4(*Ib=Vp^}b%SKz8>qtW9q940EJRKYz_9eSm1W>QxTf6%BGDt(=&|gZ2e{zSjO``Nl2 zMxoi3!QyB1A0Y|4h&fS#X~|iigdgiMbep6^+!XC3FX;%JCJ~{9hvxEkrElALZ_vQL z>MuKd;dV(-(qp`1bVuZ3lyi$#XQN%+t5MBXqgf}?!}jX}H? zIbYzyg$yuP#6h7I5-?&fu?)S4H0yh?wI8My@5?+QjPwbo{}qhQ5oyno#qQ)iPI<=` z;iUM-9hN1%yOmqOr97RNE5yJX*tX`oxe^q+EfW#O3uRShROiB@eMR5<#C4wqdYx~L zDTvH(&Kag%64a!(FT3-DJywVXLQb+OXd`_Iv%E7%4aR=GKtP0l3ghh-iBX}+lxE)# zc`seaaNwM)DhzU%q!MP(2wL@jImRnBkBLc#aRF$`Xg+d4l+5q_hIj@TF+>K-*~WJ{ zfIeTh?(4xLg*AW{qxY9%|7F+9anyGj#wff04~G%pL%ar%xZ|!OF1||!L9bqKo*7h? zsKuF!^VcB95HlK8SCuFjc8|DMTvsI!ko^DKkEC)!AY0XUx*}Z@ynqc*O>o7gKMPt@ zun#Q-oG@{p1O6ug6u5&!Dc}(m@oG&miYBS+IR*n<>y3ta;fDHxDf>NL&_6^#VZ52t z9d!rm8 zQFQrLjHp|rhuK)$5D&^Z{s<#fF;G^@6(K~GO&-V z^&&Puvjl%v6`Lbvq&A#1rX;PU-Nk1Ox}!ZvuRDK*!Fn}Ogj8crpr*?(YTSOT9trTe zp4wFp#&Rr3tF3V?*v6KRW(EphpXzB2R?AB}3E?wa3F3@L^|zX`PCjn(IpZJ)J-Z_~ z6$A7g6K9Ja3tGOOTj;JlGtG~B`6ha52yF?T^vr-PwrVu+u9x;$uCKWd=TRZ3% z5*k4qJE9ok*Il^#&N1Y^cDM|)nVBMfI?&Lr%EGHla{^BJuMniBzFfca=U6mI_()11 zcd@%Tk6!uGT6p6H6rj-uX3%$^4-x)kwwRg$;WqeFO`3~zO8H;u$p zd#_bAA42yx2*%cU9hP^rb+mF-TX+Aa!M zyrlOV?YDSZThq45c*pQX2rkaOe{Nom%{0{~C>Xog-ZBk$;IEbr3Y7Y$h7Q%JZaf{+EGd4k%fqdjwIpF_e7ZgZ6pfS3;=!&p9(^eq57aX5b$LZlAYO9YzY(-` zt^KG@j~j}&cY}_5%EL_3m&M0vghXCd?#_sU!DA$oDwEV9Y}XBUEwZTcTy=JHI&a=xZ90q<*%%rUESabOR{6K zO;Ly&U$?NO%y%kZ7Gfhh-#?uvMyXXH{{UDvie6^;<7!JP%)K^i}(5?bHtzO-LuSoWx~B$8X~HG@APT22lD(8qqUADa0z&p@2TgQ zo_^r~F_coMfU)nv@4LXgi4I68mG0Y@Ax5VdaauVQ&-z7p)#^}$1cyk-5 zbI;?aYR9$wsrOWkDLyN(z7RXVSKW8bj|3mZgqup*%{I@n*@sTp2Gw-JTa#VheY>p~ zQs;Ij!EjAQ7U-SBJU#{L^Lzi0zK3;w@BZ)Q)MurmJ7IC+*xnUI0v*(WW$n(*VZjm) z!2{0Kkt%|3%FNW=E_9xVbW87t%WV=9iyn*{0Pm zg<_HG*6m(b4s3T_Nl%S9a}T``9!I8b+6&%<05;qIis1V%eZYjp_&?T8!g?&O$Lg+v zt{by?A-9c+qY#=Lk6_4YtuEQ8#mHI#@LZp_uU#Fp>Cv% z^S^n|jE+VcVgX=iF5!;(?;Z5tRsKH|7JvqnJeh+f4x6^9>%t^A1q&j(udU-bT2=S$HYbSg6 zL1LlHyYr)e01bUDI^^`))sY66C~AyA&b>FMJu~O{Z=SFDIp2BPBba^gm(lDG>1D2p zVDmRBYmR+yN6TMu&LqBSE7Q9~tGDkszda`D@-NXkZz@VBjji~I4zCse$gAj_^`@s= z_#X27GAdXY)FQ0k=fv|N-sWwJ8CofwaRF!iVv0j{bpR>c{6NaMGyNv!d3VIB+Yf_t z@!qz9q^TGW9_RdF&GpB;0c>^BhxZtWm(;dkbC2Sm6f1VKswe9|$yqpjHR(JZJ!ts3;vfkh)uFVKV(@l%i&ZUh{8u11f z%R#&T!%u7v9mV07sQe^$Sy~anDbFY+3SS^4Z}1bVN43(Q-VB$KJ!_#y{oUEsiDr)r zaD8XFFVwP``SAMC{9;0R#d!_zyeWM3>kNYLEmdD<7^7LcCzRQ(ymz>4jr`8H2EVHd zcwo58{=_g$J*s8s;mJ(kJC0iDl4ZUN%$(~snb1BX@;mc5d0UewrAzKsakD3r?PG0f z!KZ~pYrj@#sq@xDJYoEOsl3zz9uQJ=JNr&6r+EVdhJE=}SoTvp6Q@2sce1n< zJCI9k0dc=tF?hK90BUsh_YJ&9YJF29+$ptt!8C}ZrbQZ8ZZcBB8rkw@s&UL`reoN@ zu`o|x=B+v*PCMSeO6R=m zUmx^c^vpsYh1SQBi_A%GTqr{vv^3wdzG-?IO9x|^jC<%6(}<1I`J&t)Bej1r61cMW z55V6fA4ty*BNMc;-+v3zz+2*;X-~*v|J|C! zP|aVO6@G}VCcfbIbIO_tSDOr-+Tdwc%i5S`>Q?Lf2XJl}ugo86X8I4{3wtG!-Ai#< zc#?Q}hik=|f--nqmP0uD*+Gw?TA*nw^Fh*h)UV)1&zV&29`MDCpqlC9hF>J~7PyQD5U`+_0i znLeo#Z<7wgrmgCXB_)h+^;q##1#a^d(;s0!e|$R?L}(fFBl-v6Wjt^&klOE>GLgW; zy&64U?^kr#-IoF`?Zcj9N1?d>jOsrZ(bo~S;x@Q1If?;Ah)<_B;u#4A~*2v(o3RJgSA;PVEyy4dZ;IAL9JWy8!ts zxGJuqE=tq-K)i`_cbWyArYcA3iHYQJJrUKcIJ;7E9eHbYA0xDntJ4ilwjEP*94mgP z;b#0q;wDtCUVE@;)=OP0*(zGssmY=b5u}jK@IljPZ~3DB%#1*CYczs>Zj4FDkiWXB z5Z+Qb^X8a-gXR|ma`gQ4|HbOK(XY;4e~+udsqhc^Yehr{Q-6DFu*i?A>C%8l2G@V{ zo~hWEFj-muWHe>%j{?JDrO}!4;yigi0h7O~>W=+Q1>!`wvRn}6QBUdEh>}~6GP;5m z9($iYIfjx;@ZGjyo1`_pVa>-WM!>3zcXr*e*;Rdcf}K38OBvHs{RHlq8wTO}t{2%WUz}w=xj@*QbNKF&L=R9-{aUK%?|YT# zs?}R$>t@z}WY9_o7Jv8CiEwKeyuY^dzxR{>vAqz62=E-hmC*VCKsSj>u^S&(0>y3t zQFY5|VD%LQxY8bt0U8n!kQ?AzBs`f&<9NhFI2|9b-<=nDmyczPP=lTN??qT50@H-r zglJ3*OSjNzNaH~{M!`uV80^eyziIrDNk)SrWV@8@i);;J7f%TtoU%cqN#ztu5E8Cx z;d9orpFG)79w(~aN0O-eD)oM2*UXYdHTPg{xGo9|{XLLbw|ZqMNwjw!77JA=MQq-F-xyF$d~y ztTvgDk-3u0Eg>2A+Y~3d6k6fgFVnpkSAnFY!BVp~ZN}-nm5LOM9D))C0>qiS)d}MJ zCc!2tq$c7T>LQ7gm^la%Z&`WOw*egV2sCj9Jj7;MSCNPN$;nqyXQj>wSbI3?ksemZ zsT7@Qe<13Tuu;|%-eWUNuc&2uASrBi*(H<{udKablhS{cw-91zWdpO({Oc;eDE}2U2xw%NjX) zO4C9nlKy;>bh3&|ta>VEhDxu|&_>z1_G!|FY)Sq3$R*NvU?5RNl}qI9LfD60EV0s3 zLgAT-3%gg(+(jVNm0Le217m*Z*2Fq46{Qile`Vl+SzO9F)1IihwnR}E-g$$?N%&BaNwxcYIL-A{w zYbmDvf)!`JjL`aoIKg1D)!6v5>ynO4sDL4%*QKKunK(ZZ)sLI*wLxpi4I`<2;}=XL zuV;v%7mBPE1p=&>4a&t``W+EVDEkpQ<$@0qnt5r90^GpRs<3JEt(@gPnyWZ2x z>9v*%JYbd{X2|jqUf24;`jBkCX_bxS5Mz^Wc*P+vzcFUyv8Of(n@&^T#GxBvbYs{Z zYo>cW>MI1e!%treb+#k~gbGCBb-*DDwJvNZPL;1QkA9oJTjO5O}l<=$~mT63xtG?`TzA-|1X#O|4e=34~CQ)=Tu0L&90YAMRR za@!r3nsAe<>V|1pKI8E$zYWa~8E(VyfZ^WBW?g+|C*C>VjlVpBE+9}$qwLV5Gn=8Nq62@q zi*5|b?TF+%@!c|GFwZDLR^@>~mpY_DLLL;tk4_tw^&M~udaIrfNi+PFYK2(|FT3V` zI)b7RxPv5W=&}(D4N;Hj}X#_U+`? zYO{^@%9j~BX#PFd^x*l_<@E4iQrFjZznMZreqcJAUvRPDkjnDkTcUSD88Oust-50e zX?GfRhYkQvL0j!NNXN-n*TmKImk` zmVyRJ1&O%b@e-R8p-Urhd*Jcgx9xj+QPEpJSih}`8QB??7A7Lc%aWcJrM@S$CKoIJ zd1am@lAbN-W|dA%AVx$rFsxjFbV8`bxJvSsnxIZjH)OxO5l5nOQF9}rbqS9F9&EP7 z5B&O?(Dergwk?haO}Q5ddT?t73M)3g+|ajwgPW5^4KSre^06~D11l0~#THUpP4(7+9{k)Z?setEkgJ{ zqY7o7N>jIMzZznuXdX-x{bhMKUt^?$K^f$hPkXcc?5@>XmTV}!fmNcxTs6+1GpfP@pCF&O!1WD0+ zwfnrNCYvTQ>&}i>q95(in89pVUYJp%Skv5);-{}*)o^nE$%7M`Y!@{-!(;$sd9$AW z19jjcsh1rQ-@H^ChlZ>2h~`D~oHdB`q|p@4x|l274HGTOlD}hzN|{X>wWk<`7fOIU2u9G zP3{_#a`duX(ww8;qFkP-oZmkH!Ou2Qw-kG=A<|mx>Z}LGqmo+hY~J5LF%$h<6Zg%6 z4xYH9@I)Shg-B%h?{>cypYmhf4>ax9Jiyx@s7}efZ!*tpMFl6`o|s*%dXy^`TEXHy zGS^l32!zp5pz18OB?*(73BYHGXF)n#%sk+No4P~~2> z^5h`)Cq~S`SWhpn23no7B-MC3?c2AyycBtIvI2AckNk1*@@QqOi+hn~jf>CzZCHX6 zD|1f?BKv_g4`xnMaVeG*Zw03-F^2P(ScH44^HFL5-Iz$zx6{UC<*TX=1SO^m(YWFpxY_V|2cku{+mu7+z?mH%9}SpEl5A%=57oE7N*v*5Q~qsw zK0lXsj;H|PLu(rg*Eq0VbAvCHnt0>7M0tCYfeO{Wrd!oHiaz{%f zv_7CI8vuB2T{mH&uBEt?0c@LbX@;klD(~nibaB3_XIyb50CAU$B33)B^+YsaFvvE{}I>F{}H?5 z|93e;gIVf-sb3lZLNj(%OsKUQn4I7wRl7w3hz>`vS3*zcSvKiP`T>P8XaBINw=(?#c;@CKX*E7)V5U)H|b)E zUfc0k_V!Q-0zPh<=J#yYNd-wwZ5cAqz#2R>sftUx&4Ulh`s!|$UO1oWjTs(2aab=& zCS_bN&e*Ot9p3*8W?B5Yu|+3{OtTx|tQv6odhc9lMXsjJ=uTg-*#mwZ=irf$skvuL z&UI`1=I?i&@RU44+Sn@6SVJ3Uko&GL#Sio4g&z9io*>7@cCt~g8@8hyZsApIbi64G z5^eOwKnDbF?hl`|)$Ja~T^^hb$a7AT;F?d!YRpRVqhKSJj1iu0dQQJF6G;6aaJJJm zERN18PJD~F#QFN%u3h`>NQKscfRI{^jrVQ{mpz03>pM|*l?vJjhW{ZYZ^7#~Z!=c) z>y9h?!wx%9P;?gkG_=j$lRM>*==4uR_(SVWc;21i=~-glQ+^^qInxWD{O9M))Xa2L z(rIE&4`X9bKpZz81C2NAx@zx8+#y@s7p5U-FnGvll%(DN)7)1_McIDs4kew^Fo^Wf zT|-DSbT=s7-57Ka4TB)f&;rt})R5ApfPf$%AV>>}c%JwD<$1qfo$q{Wo%7eMXU(i< zp19ZE_r9)cUpvjelFDlve_%w~X@gaG=L8YKC=U+7#)m+1J{O6TsT@vxatYmva1GWk1{u%K>!n-OYm2 zhoNciGzI3~T@CnPh;~xj@N%?Sx!<%mhkb{-D3`nBAe&$nHi5FL?8&yFt#j0_hh8EJV(9xMvy*}jtZYkQ%Gc6|TI+};GOdL1@yg&nUsmnO8(T=lY zb{-p@WNP)mb->g`5toI964t7Hxw)eXX{*oRB{h0QXWWfkfRDG|$pDEu%?4vhQ^(v?53WcGHw0Om zW7=J;*XoH85mXZMl+dbQ5FS0*U4F@LGgU2KM*Dya(ks(TOX6(4P3y4JHH>4bfHfkW z^03w-dV?e;`B8B^9@A8*bim7?G<%|k?rOj7@LrwA1dePkS75(DO`M$;t%&<^T%{eC z#aN$p22$Um-d#*?7Cgmo9A+yn*U>sO#M&QOMY-#5sg(=VtcT#t(W*p08Wvxd_e5b7*P z3?cee+M^Kgv?!1FFOgBOC;>5nJhwq2+>WB3!D@`v1w#b2osKV?7(fIU<_nLJ2HY3UWN5AyTBpE@ zD#?TgBL4)l{IT}@`)xsy4qj4O)x$FWFV64B=CkTb_{{V*vB@2Za)GEE6-FZr#QQ() zPW~9LArAoe#bWfMz+y@jWv7y;aEUT%WEn7JK5?#vPwb}Vhd%63IYVA}pXN^LMG^e> z9SiN?kM(Ks?IT3O(d((F+fRa{%I8 zGQq=n`Lhxh52N*F9yRH-K`NVQ5&E;tjb1+t;qF;UTZVaNT{QMb5adqbl=ym?n8}fB z2%++vwpqq=Z0jHT>btLs?QNaP7QS#SeBs0eJ(*j;uziA;!j`RDOi1i>95U-na{;c( znV?{bw&_?AN3N_TaN?2^(CYYVPPOpXyP);nRWG_g3F#I|fcI6)Wnm|(2FdezL(&tp zXNyt#cpOU!UiSFyYejf;=y~_pF+x(T<}o63UogcFM{tZvJLm0!FOiFKlYvzb#k_t9 zLFS|@Lx1XMK|ewh&+NIDCd9LatvX^9SBbZHL*L)AMUGL~8RD;ZHv3>%hSns)!}*oF9@6M zPP#u%n`)&=>r5Q3iSy(Gjwv2IS8fsMY^D8tCsjZXXP*;S^MdS!RxtiW`E(|*+Qfl% z2NBLhQ{t`GdtZ#<1fYZeJ7yl{9SXRC*v4U>jo z*yPc}r(MKgX3htC1v!dS2~QoHQcx^eCO>{Hy6{M_S{v1>OYP9Z$}T>>Hc{qeGSW6Mzy2omBI;~Ht6FSxA9$x zWbu*=d%EO{gZ0=trJj{iT%JW9Rx-o4coPkJJ@2+`bFWikn-PIdr{efKJ!i*(TZyuR z4Fd!A1qbmbDoLaiI|97q8GEaQO=~?`R6L!Y`WruGd&N~2ZIe)6Ek&35yWP%fVw33( zqFobTT{@9tcwqA}>kt)v%;tW!P-TmHvva>KsWdp9Y#^I-fKXf>S7O!p-oR18+T%;A zyNMzl3u1$A*LZ@|fx0_wL7r5w*&XI!JZ<0Nt@g}4iHAD4AMc3PI|$C-49F7eE}5yH zY=8B?$#+jsr$4SEsI-koy`G=@pLg@zJv=NY>(O~S)JGVkx$tf_bsN|xiLR=7PzTe@(m36+FJTKv3*}-QIgpkfr>71#bDoaBOe}v7`7YJZ@;v`PN3Y$_@Fb zdTkg`r<0~OQ`X>fax<&)W#hp{J>6T2@9+sD<2tq^WW|<(V4`sCPT1m zpP)aEIY@DtYwk37CHPrtAt&;!C?~N!xOy~43*sjD;IrX7U_ATzy-a?felM=#d8sYR zmVos*Sf`65@J`k6fF;GN?L|d-{lX@AQu2A*G`Xv!3D8#XFK~(G)%eQ=BpRwD_GAw)q zm1w2jMjd#0;U39kX{pCtIVQo=hfw+?Y*zd}=Iq02>9G9wOk!+Kn<*Mf)&uO;D?^+^ zns-@HuQKRMAqHIE39VY5t6*@c-pih5h`qhgTl>C=wXJvpo3s?fz?rQR1HX2!YN!;I zCB~y1aBFp8cO27Z2At!cN=x2-a941omL)9e8a)|s6`oCW?pFc0T!gut;jo&2 zPfm9SNqJHAbs%9z1>L70Iz!iHG9@u+J`4Q2>~_YsR#;&zx{;N$;5Unv?1Yc0`>M)d zdBA-O0I{qM$wq0??b(!>#fL3^EI5n9>!s(AqqL*c$q?rEme9zr2DQ9fh#k{0A^Eh= z7`qaHd*?OKkcnIJ;iCToDwx2{F~f==%Jxx+*etT8DK?H-vsy=;L{SC5EumIen>9SE zVXu#66gm23Kw3rni9Tkn7lx8Qi#nWo%Qsqv19sg2kW9O~mk)IRUbyf#qKQUW_W^I| zPLj$5D>+kG-ZU;4NcG8N{IUWSzVQqfV47kxh0!T92SDYelNk{vIRg}5{{W=;Loh~b z5zlp}*^>u(I8FQ>{HgylMB=a41uEl#0eJWxa!5 zfIKzwk-op4rxF$xCp=dz6N@y0=$3=7D9z8!MJBVXF%Zcv!m5Yv1K}tsYX8S~|BoS> zf0kMP1-vssu5$-a;_2Z3CR_Vksr(n6$X{;@3-BHL_kRlVueb|n1b;I(!==&%k~OgF zLPL@3V4MBp;`q-OMlhE(vI5n2Dkhi8nh<#*J}d_Vp3PsJ&OWcGV{F!n?BPcu*n9M` z$vbJe}aYvq6+aT zGsnI+jB2tj;q=+#AnhKl%E?leC52U4ypw|hb^>D_Z%=nTk{YrGhoqQKd&BxWJ$S>( z@+JBcq|G6AaseWlMk{SgWlyfJ1b-fukwIn;W|cp80j@}U?I~L;uTEahu1wJKOKznQ zCjS5$cSEk)lTB7+CC+1u*DQ{x7xQm!W!Kx0X;u8{zTBdpKfVi3{WJWd<#RoceI}E2LDDYGI@R2+P{1w%bUiBQh9_1GgZZtW~DZS zUP^CqnaiLZv67vdwqlk<2pEiu+osb=Ou2U%*Cevo)e4;*$wo{JOfA?azX>h-`8w8A zgH_&f~N1pudQhJ{s-aUFaqc)VQ>aZXbIbRV*_{i%Pit}-XF+w%pQR*NE z$^OBxn_#P)xW~M)$K+G4DMCT`(_ODZL~3~cec!@2xqEsV$Mz9k&EEqzEwflYHS0zh z_%HAptD}8N5wFFUJGN*q_VZ&=D^satmzO(Cm#a=o^V7WYOz3#3JNXI@$SyEFS3%kx zB9f}cF+N6Ytv(y+1I7Z_mdtga!`#jjiSk;Xn+c*v{s_tlmoOyhtQ$~;8Exelfx^HUE4TaUq1F2fRKMKub=bc22J(!lorj*IO_rp~ zKI_$Zid)KBGi%O~c+00;e>1I3i~eOA)4=lxVynmllTZ}&`b#LGw<=qkD#2wUBkQfU z@puZin5>-Xr)L9DaVhr`@y*VbE2{7lI$kbt52|c4!=I64?}M#QsYTLFtWmv;tIzTg z#Fk`aXzjh70@hph7l^61T7$bo(51*{l$*|17Vkdve;q{Z(XyJ`j!l2jQi!Z%c#Y+L z3%Z4j23~cV%MPNi@^lFF;YHxJW7ef22@c_@A?J0f-lQ|?0Z)l3K3mi*+6^2kbKac$ zw`H53Ny8uR;4V`j-Up*h{f_8uF@K0^&ruX*t9!}rpgA-58K*Q1Kag4y=?^7wmojQ2 zmdw#eBIv!#R8U3-mcwA&Jv4)ausH3Sd-$N3vPoMbe%wZ1o@JcZEpW*gvq6;Rl#6H) ztIGZ|-5DGl3c(ZVWM{r?tyH{&PEUI=)~eY_dD&t<@(wDEJ0}@5qP@`*s~GoEHFqY4 z1Y0-IflkpM+ky9Yp@|H~<*hIlKS#?Bm03D=-%FIK5QZ;PbCXY|ecUh5s_whT=hXsI zqC>`}!6Y#>cawlQgn3DTS-Ff1YaM&bW4i)PdxWKugefo`_`6*$9vx?&-)vJabGkBt z+8YCg&>lR82OAUjIDUa*27EZ-1Q7zXibo31w~5N)CckG>uejeXxNtdVN@DUiJ3gYC zHLiqyn^nSx>AW)}FA)ySqGmQdpgwY$&ULO`ykERY+T5h7jBAz9VrDXG9%f^wWRS+R zus=IUs2F&l5!T#zCWp9P^ReZkWVWt&0#06;$Lld=?U`a+-bR<&r&Mm2jI&fB{^5RE z*NDrWV_HkbaQ{vTWza~x)=%{OUW&IG)4q$R`@-Sr(rojsc%MQ9wU^jkY)-1&ZIkbU z@@F0d3bf(}4k1AB9|kcUt+HaDOA@XyevtoX*#2)&J}B^CB#Hk$^b>+M6F4xS(;?GX z*e<_^dOQyDE`SK;oiBQfM{4U-@hGSiJz{7^tM|_zC(76f_;gB8bU5DMf21DD0s6$> zirxfJ5iR6F5vbXb{3LuWLv22@0d0b?hk96z6O`+O;vr`n0=&R2xnObu_sKK2dN9gx z8=7kt5ecM^LiC-7%S5A!Du^jawhYXO>8mB z9FwzbCapdB)3Qyh7VOrB$cDj|F0LlrfaTEGm-O=ejjhnu3qWic6;w$1dM+5nS76tU zz9tSnHc2HX3DxeX6P#Ql;&Wqw$clS3pv|o=J?y(ue8LwUpEQg6f>R`4?q?>!#!uJj z{@j388HL&4`|0$JR*NgH$I@yHtl0Q%=1z=uPE$SoZs%-}VAsK5 zOzQNnw-NN2xtHTOkaBl7R|%4s9{-+M=TD zmN!s6*W}mId^tl%@POKH<-3tc_OPNSQ^8(=>JGd(naRvHGWrDRY0f`11MRlXu80Ri!tCu}li)hXlZ$Tz zuRHGqmGk3ta5te3IeX`;g}A4+_4s8x;X%CEZZcuo;p4=`S;WgL^cd>?1-gpA>XVIa z4KNm?+&PQ)hS=qbvkxUc5*^zp=(yKnjj(9&&{kMF51@J>WgEjY@xdkQ;!5wsO^cM! zNj7cn*dmm*Lz2;FP-Rc@?9HLY`o}fV#hYhzZ3U`t_$AWBypzsyBNMGghdn)xD!gXL znI(FAM92B&4$x7QcOZnm`-OgigW$l-Bz+dUa{ zwEnT=d&7HU$jNga?2dyv7-i#Zx7`{W0OJT#=P~P=cTXGtzDgJubbbDXHpm)prEqef!#OOZTqKF2AK{ zvVNIU7%J-$iv~73O-I7~%S` zSD(EZ&T-MFm{*i(@Md<zxlFP>;z`v8aEbq=1E} z%7vCY$csQ&?ks(m4>~-vsWUxJ95GbX9fWUpG&X@wWFvRo&L`Zfc4Q3N08jRYfv?OF z<~d5e5ytWfKHdyljK4s+%@wT=5(i5YVE5yP`F&qmR5BF5zNV%y>QgFFKLF87Br02K zYg*y_*j=tSUsJc2e7Rj`3kwckmfqB#X)u;BUs9md(sPRECHy2hElQ&kPWLPyyG{;e z-uw}$T<JKRq^d183k)Yf7UonRsCLf$#{VC3KxG;|Fy zwyDda&f57*dxysGRCsqAf2mTW=-XZ4wA*Hm702yua@~iZP&LF+`~eOzZBWly1|sO}UntD1>#Zs6H2OPuOGaK=%L>{I<0$L<4I})$ zU|J5tHuD$uuh5CXC?j`(PluKDAt%^McOs%o!rM!tu)rLpwIDzPsWmG#I}+x{IQ>FF zL?;kgW_I`0a}=*Kheu{6T{Lw`=6MlFtOC!$`TcSfe@U`q3pwNOi5$L}6nyymo$1k< z;bq7xl8bQ2Ly*({1T#wH-2V)N|F5Oozj^@eBSmC?$6o%&n}Yvrr|~~~W_G>qq@fF3 zWJ$DMQAq-}86`QMn!jk&eYxdq-&f_+lC*dtLE02AVlv8Jy)4uEndJ9G>?+p3pZk_} z!NzIfltJ}E^p?=;)&*78k<-{QvX`(Z!FumPTRgYd^JKx{S@P zRLKYKI z>+yo?V&@0u_dzPeQVMV$WFzjZ@RG1GreeAB`n1Z#ynp4jj}Xc~_)(2Whckuy5P}nJ zSzFf4SY!YI@4Q@tgF?|aLCYUB_IbGv%>}qNoKg#rTcEuY^Tp$q8(DmkcGpR}M1q9C zoHhc%fN8OJOI0)www_Zqn;2u2QY z(;JKYs;@(QLH2J*A6LTD9&B>VE(}f0XL93WKiykDb`3hTBg<@hd>j|CNyvN3`&4z% z4unQW?`dNE#4*auYkVK(#K(2A^DlLxek+BVrJ(`IelQ})pR zisqXX?5mMcXU_ff+0WzmJA5m_$DJW!=k-FjQu5;KcQ*0@D3|WX*RrOVo@cqM^#)98 zA6gpLZ`L>)9c|*2r1wdSwoc~$0zsRjg0Qb!>v?XOZW&YJz3Di}j-YccNfU_s(WCn^ z3)?3&cZZhZzhf5%Nu_RCiw|`?n{avUzofENwR=3Y$52i^38OGOP$v$VZS>~Sj!Zi= zHKtiY8b@8qnhqU#j3bC_;dyyztz2a{T>bU!7hjnsf{6;6*i<<4N9H!|yb+s*J2C&!dR z`uHjjIo6Z(Q18u7B1*(JqgTFmfzMr-#P$ay_KVb z5x>lW*9oLxC+Pi0gz9v(-6Lb)81{9{l&U&pO8zLXNAwv@M>O0k?l}SLz!8{8l;N7f zMg&4PAmE0zwGVfrU!< z+lN04SuLsBaA|ZHsrIEczm8gPpQ7- zDuWGyQ_eSJChi_bEbYmrnBqI+Q2M0EV2q08;^c`zRptW42mBI_vrkdDpPxDE{%g|n z-vwL{O%Ys&2m+hHAxq$Q>idn|BuzjAdQtxdw*A)#jZrI}R8?9l870uHqs#$zQq`3M zNKG&Y!1ZwWlP4}BqJXR?f2W|iMm<4NCylO*@jy4Y~|4Zs0G6Cl7{HWmI? zLCrsgDgSRX@&8Ot`#-!$j9NfARvZS>FSUH?j57h#D&;7G)9S(vs6bpL8=R?pVrtug zqjkaN-UV6p*re)C!n{Pqk-4+9Q1Pp{G4E{^nD5X;<`d;f41!-EY6IcuK*JI7+bjoO z5#L~}$^bdu#2#O&HFX1qGF zeEyYrE$z%Aux8`qlQzDt>YoWGUR){tg>l?UTNYNRU>rx2k+CkqaJMgymtH-ix>0&e z)7YADx=`At&&MN2)<>ya+)fv6#WVaSjzmRB8{JY#IEbXlR{h@2Be_wq&=)SoP!gmS zZlE<6e}J~)iQA`Lc71<|x)H}1%4^dzDdUp%LYPhBI5=SCzzX(b%KygpM*m~lyAg4_ z(j;x67eU&5>5SU*z&yls6>WoGQ}QWb2`#;;{|oR_@IGE*}q7AuYFHRQ@@C{WY z-ZE%krEscdWV=O>({9XVq-zDZeLbf}J+_b~Y1Aw|oze`Uv|uNuF>EOf+-eD|`jRKP zxwAjEygc-Ql%;J~1w3qGB4nQ!BtErPKXMtEKEci1lmkMi_pEeV`B9KHrR3`!95gEA zl$~xNT{(AmV7`u6f2qgG%!+4F5NFGTAdV%LTO@eWi{IbYcDV;RHQp$npG%pim>4Tm zWJ=nhht?T&YrRYN?4o|)3Z6~r{PuZf%$A~d(ip}PFt;LnQ_yr(j=-g1jF>-`cBZKT z`?Y8d&Bg#1d7R3EA-8KQ0K+vTdHdhaGJV=n_<#)D4q)c>jl)MJrj{LX5C&0pz z1KR;U2k<21NQdZ(%c}d0(rnq9JQV1Ib1<#nRSQbEe>^?Fy%f=F&F5Be6q7^tU~(nl zSRCl-fMbI|MJ+i)vrg(HL{0(;2oJzO5mOZP6_z(0G9%x6M-$SE{Bx!Adu9Sxq66D^ zqULra7(m=`=zgQNu*o-kHOtIMP$j^`WD>S&HK4KUuO-88S_X(YP$UsG)BZ_)<0XM) ziat=6?;=EjHuE)$p~-jB!cSBs=VQxY#PX5?YCX={Y7uTgZ}fk_)cx^3|0Top|MbTg zY*`_*I%E|k`8p0|V9t?=60z$Ef=(w{5$=2?xlj-Ym=?2EcYI6eJ4jJLKA*S{-%Ftx za<434J(ePvwC)aq4({f1wTaxzv(R$39-aV?0rO2%1|0&#v8ULP^>`J+Vz{TIN!4(-lTl=7BM!0}nDZZdiL`skacKzN*Z-GTX1M zqK;glNh!ZTFA7V3fqpR69h+M6ZQ2Luzw{vD%Dry1=++J%o&NuLTd@hZDfr+WqlyY!bIrl>%~solDNd6ig|j0VH48zAip8PHdaQvY(<_2E zgpj_yyrhH3Enih}3-5Spl}S%!Ey=68wNPWswA1xXpzU*TOfO{F+^yyI0(_NzO2j_s z!Ir$Y9Zs$cObQVAZBnZb(R+3<=Pn5gXPB43Y~@mpgs;%im76*_xzRui0Wj`&DXyn8 zoI!4KrxK_bOZ@!C@YHhr)^q$(i;R;cB5P|8Qx@-ZNN&AY41-_#~T#F}0%xk$U9=b-MY z)KhBP*3`WYxviLu*D10qSqP2repOF$gYPT!jBZnL>{FaEjV&-eMF08viI#9~auR;rPbQzoXWjJCy-P@{bq){k+1 Date: Sat, 15 Nov 2025 14:11:49 +0100 Subject: [PATCH 023/157] Release 2.217.0 (#5946) --- 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 8105a407e..a31ab334b 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 +## 2.217.0 - 2025-11-15 ### Added diff --git a/package-lock.json b/package-lock.json index a1d1551e7..37d127634 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.216.0", + "version": "2.217.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.216.0", + "version": "2.217.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 5bf5e6775..16362b1de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.216.0", + "version": "2.217.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 9efd1da17e3eafdf3255a3946f5e53edc82d9d68 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 16 Nov 2025 15:29:46 +0700 Subject: [PATCH 024/157] Bugfix/missing reflect-metadata polyfill in apps/client (#5952) * Add reflect-metadata to polyfill --- apps/client/src/polyfills.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/polyfills.ts b/apps/client/src/polyfills.ts index bb1da3e23..df81e917b 100644 --- a/apps/client/src/polyfills.ts +++ b/apps/client/src/polyfills.ts @@ -52,3 +52,4 @@ import 'zone.js'; // Included with Angular CLI. */ import '@angular/localize/init'; +import 'reflect-metadata'; From 6edc919f0da75d7fc3482a82e38d7a75467cf156 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 16 Nov 2025 10:39:37 +0100 Subject: [PATCH 025/157] Task/ignore forex in search results of FMP service (#5951) * Ignore forex in search * Update changelog --- CHANGELOG.md | 1 + .../financial-modeling-prep.service.ts | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a31ab334b..51f060daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refactored the get holding functionality in the portfolio service - Changed the user data loading in the user detail dialog of the admin control panel’s users section to fetch data on demand - Exposed the authentication with access token as an environment variable (`ENABLE_FEATURE_AUTH_TOKEN`) +- Improved the search functionality of the _Financial Modeling Prep_ service - Improved the language localization for German (`de`) - Upgraded `prisma` from version `6.18.0` to `6.19.0` diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 90035b1a8..6fe928d7a 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -504,8 +504,11 @@ export class FinancialModelingPrepService implements DataProviderInterface { ).then((res) => res.json()); items = result - .filter(({ symbol }) => { - if (includeIndices === false && symbol.startsWith('^')) { + .filter(({ exchange, symbol }) => { + if ( + exchange === 'FOREX' || + (includeIndices === false && symbol.startsWith('^')) + ) { return false; } From c2f149ff62d3b63f5393eb0708e43e562c8aae9f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 16 Nov 2025 10:43:17 +0100 Subject: [PATCH 026/157] Release 2.217.1 (#5954) --- CHANGELOG.md | 2 +- .../blog/2025/11/black-weeks-2025/black-weeks-2025-page.html | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f060daa..029675b83 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). -## 2.217.0 - 2025-11-15 +## 2.217.1 - 2025-11-16 ### Added diff --git a/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html index e10a64de7..c92616d66 100644 --- a/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html +++ b/apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html @@ -4,7 +4,7 @@

Black Weeks 2025

-
2025-11-15
+
2025-11-16
Black Week 2025 Teaser Date: Sun, 16 Nov 2025 17:21:07 +0700 Subject: [PATCH 027/157] Task/enforce module boundaries for ui module (#5947) * feat(lib): move ConfirmationDialogType to common lib * fix(lib): move SubscriptionType to enums * feat(lib): move validateObjectForForm util to common lib * feat(lib): move GfDialogFooterComponent to ui lib * feat(lib): move GfDialogHeaderComponent to ui lib --- apps/api/src/app/subscription/subscription.service.ts | 2 +- .../app/components/access-table/access-table.component.ts | 2 +- .../account-detail-dialog.component.ts | 4 ++-- .../admin-market-data/admin-market-data.service.ts | 2 +- .../asset-profile-dialog/asset-profile-dialog.component.ts | 2 +- .../components/admin-overview/admin-overview.component.ts | 2 +- .../components/admin-platform/admin-platform.component.ts | 2 +- .../create-or-update-platform-dialog.component.ts | 2 +- .../components/admin-settings/admin-settings.component.ts | 2 +- .../src/app/components/admin-tag/admin-tag.component.ts | 2 +- .../create-or-update-tag-dialog.component.ts | 2 +- .../src/app/components/admin-users/admin-users.component.ts | 2 +- .../holding-detail-dialog.component.ts | 4 ++-- .../login-with-access-token-dialog.component.ts | 2 +- .../create-or-update-access-dialog.component.ts | 2 +- .../user-account-access/user-account-access.component.ts | 2 +- .../user-account-membership.component.ts | 2 +- .../user-account-settings.component.ts | 2 +- .../user-detail-dialog/user-detail-dialog.component.ts | 4 ++-- .../confirmation-dialog/confirmation-dialog.component.ts | 3 ++- .../confirmation-dialog/interfaces/interfaces.ts | 2 +- .../src/app/core/notification/interfaces/interfaces.ts | 2 +- .../src/app/core/notification/notification.service.ts | 2 +- .../create-or-update-account-dialog.component.ts | 2 +- .../create-or-update-activity-dialog.component.ts | 2 +- .../import-activities-dialog.component.ts | 4 ++-- .../common/src/lib/enums}/confirmation-dialog.type.ts | 0 libs/common/src/lib/enums/index.ts | 4 ++++ .../src/lib/{types => enums}/subscription-type.type.ts | 0 libs/common/src/lib/interfaces/system-message.interface.ts | 2 +- libs/common/src/lib/interfaces/user.interface.ts | 6 ++---- libs/common/src/lib/types/index.ts | 2 -- libs/common/src/lib/types/user-with-settings.type.ts | 2 +- .../src/app/util => libs/common/src/lib/utils}/form.util.ts | 0 libs/common/src/lib/utils/index.ts | 3 +++ .../src/lib/account-balances/account-balances.component.ts | 4 ++-- libs/ui/src/lib/accounts-table/accounts-table.component.ts | 2 +- .../src/lib/activities-table/activities-table.component.ts | 2 +- .../benchmark-detail-dialog.component.ts | 4 ++-- libs/ui/src/lib/benchmark/benchmark.component.ts | 2 +- .../ui/src/lib}/dialog-footer/dialog-footer.component.html | 0 .../ui/src/lib}/dialog-footer/dialog-footer.component.scss | 0 .../ui/src/lib}/dialog-footer/dialog-footer.component.ts | 0 libs/ui/src/lib/dialog-footer/index.ts | 1 + .../ui/src/lib}/dialog-header/dialog-header.component.html | 0 .../ui/src/lib}/dialog-header/dialog-header.component.scss | 0 .../ui/src/lib}/dialog-header/dialog-header.component.ts | 0 libs/ui/src/lib/dialog-header/index.ts | 1 + 48 files changed, 51 insertions(+), 45 deletions(-) rename {apps/client/src/app/core/notification/confirmation-dialog => libs/common/src/lib/enums}/confirmation-dialog.type.ts (100%) create mode 100644 libs/common/src/lib/enums/index.ts rename libs/common/src/lib/{types => enums}/subscription-type.type.ts (100%) rename {apps/client/src/app/util => libs/common/src/lib/utils}/form.util.ts (100%) create mode 100644 libs/common/src/lib/utils/index.ts rename {apps/client/src/app/components => libs/ui/src/lib}/dialog-footer/dialog-footer.component.html (100%) rename {apps/client/src/app/components => libs/ui/src/lib}/dialog-footer/dialog-footer.component.scss (100%) rename {apps/client/src/app/components => libs/ui/src/lib}/dialog-footer/dialog-footer.component.ts (100%) create mode 100644 libs/ui/src/lib/dialog-footer/index.ts rename {apps/client/src/app/components => libs/ui/src/lib}/dialog-header/dialog-header.component.html (100%) rename {apps/client/src/app/components => libs/ui/src/lib}/dialog-header/dialog-header.component.scss (100%) rename {apps/client/src/app/components => libs/ui/src/lib}/dialog-header/dialog-header.component.ts (100%) create mode 100644 libs/ui/src/lib/dialog-header/index.ts diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 37ab1c0f6..0458005c9 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -5,6 +5,7 @@ import { DEFAULT_LANGUAGE_CODE, PROPERTY_STRIPE_CONFIG } from '@ghostfolio/common/config'; +import { SubscriptionType } from '@ghostfolio/common/enums'; import { parseDate } from '@ghostfolio/common/helper'; import { CreateStripeCheckoutSessionResponse, @@ -14,7 +15,6 @@ import { SubscriptionOfferKey, UserWithSettings } from '@ghostfolio/common/types'; -import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; import { Injectable, Logger } from '@nestjs/common'; import { Subscription } from '@prisma/client'; diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts index 645d43253..1127e5629 100644 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ b/apps/client/src/app/components/access-table/access-table.component.ts @@ -1,5 +1,5 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { Access, User } from '@ghostfolio/common/interfaces'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index ceae50f01..d8f08ecc2 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -1,5 +1,3 @@ -import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; -import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -18,6 +16,8 @@ import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { GfAccountBalancesComponent } from '@ghostfolio/ui/account-balances'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; +import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; +import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index 78d4bf955..3f60cb8c5 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -1,7 +1,7 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getCurrencyFromSymbol, isDerivedCurrency, diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 9969a59ba..b2ef4f17b 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -3,7 +3,6 @@ import { NotificationService } from '@ghostfolio/client/core/notification/notifi import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { ASSET_CLASS_MAPPING, PROPERTY_IS_DATA_GATHERING_ENABLED @@ -19,6 +18,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor'; diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 5d1138be8..0b4b36d06 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -1,4 +1,3 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { CacheService } from '@ghostfolio/client/services/cache.service'; @@ -12,6 +11,7 @@ import { PROPERTY_SYSTEM_MESSAGE, ghostfolioPrefix } from '@ghostfolio/common/config'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFnsLocale } from '@ghostfolio/common/helper'; import { Coupon, diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 1dd150ac5..64e7ff7cf 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,9 +1,9 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { diff --git a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts index 23e6ca271..dfcf300c1 100644 --- a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts +++ b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts @@ -1,5 +1,5 @@ -import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index 899aadc6c..ec44b6e65 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -1,12 +1,12 @@ import { GfAdminPlatformComponent } from '@ghostfolio/client/components/admin-platform/admin-platform.component'; import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { DataProviderGhostfolioStatusResponse, diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index 6b79b8fe6..a891baa45 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,8 +1,8 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts index 487a4d498..323609a48 100644 --- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts +++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts @@ -1,5 +1,5 @@ -import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 6b3335927..6c366a16c 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,6 +1,5 @@ import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; @@ -8,6 +7,7 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFnsLocale, getDateFormatString, diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index caca0c2bc..55574d202 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,5 +1,3 @@ -import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; -import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -22,6 +20,8 @@ import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-credits'; +import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; +import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor'; import { translate } from '@ghostfolio/ui/i18n'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts index c0926150f..0e297bc29 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts @@ -1,8 +1,8 @@ -import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { KEY_STAY_SIGNED_IN, SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service'; +import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index be0842467..9aa07feee 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -1,7 +1,7 @@ import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; -import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/user-account-access/user-account-access.component.ts b/apps/client/src/app/components/user-account-access/user-account-access.component.ts index 38d34a4e2..da2e8f508 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,10 +1,10 @@ import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateAccessDto } from '@ghostfolio/common/dtos'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; 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 025ec0f7a..ae9183c13 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 @@ -1,7 +1,7 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +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'; 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 a36ff3229..32e3d132e 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 @@ -1,4 +1,3 @@ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { @@ -9,6 +8,7 @@ import { import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { downloadAsFile } from '@ghostfolio/common/helper'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; 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 6dabf2f78..57ccf0f18 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 @@ -1,7 +1,7 @@ -import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; -import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminUserResponse } from '@ghostfolio/common/interfaces'; +import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; +import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts b/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts index 49c6dc5a3..a3bc053ee 100644 --- a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts +++ b/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts @@ -1,8 +1,9 @@ +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; + import { Component, HostListener } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { ConfirmationDialogType } from './confirmation-dialog.type'; import { ConfirmDialogParams } from './interfaces/interfaces'; @Component({ diff --git a/apps/client/src/app/core/notification/confirmation-dialog/interfaces/interfaces.ts b/apps/client/src/app/core/notification/confirmation-dialog/interfaces/interfaces.ts index 8788e54fe..449201a76 100644 --- a/apps/client/src/app/core/notification/confirmation-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/core/notification/confirmation-dialog/interfaces/interfaces.ts @@ -1,4 +1,4 @@ -import { ConfirmationDialogType } from '../confirmation-dialog.type'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; export interface ConfirmDialogParams { confirmLabel?: string; diff --git a/apps/client/src/app/core/notification/interfaces/interfaces.ts b/apps/client/src/app/core/notification/interfaces/interfaces.ts index c58c7fa28..071597691 100644 --- a/apps/client/src/app/core/notification/interfaces/interfaces.ts +++ b/apps/client/src/app/core/notification/interfaces/interfaces.ts @@ -1,4 +1,4 @@ -import { ConfirmationDialogType } from '../confirmation-dialog/confirmation-dialog.type'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; export interface AlertParams { discardFn?: () => void; diff --git a/apps/client/src/app/core/notification/notification.service.ts b/apps/client/src/app/core/notification/notification.service.ts index 9c31aa7bd..849f91288 100644 --- a/apps/client/src/app/core/notification/notification.service.ts +++ b/apps/client/src/app/core/notification/notification.service.ts @@ -1,3 +1,4 @@ +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { translate } from '@ghostfolio/ui/i18n'; import { Injectable } from '@angular/core'; @@ -6,7 +7,6 @@ import { isFunction } from 'lodash'; import { GfAlertDialogComponent } from './alert-dialog/alert-dialog.component'; import { GfConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component'; -import { ConfirmationDialogType } from './confirmation-dialog/confirmation-dialog.type'; import { AlertParams, ConfirmParams, diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index 08ecbf15a..5e18f25cf 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -1,6 +1,6 @@ import { DataService } from '@ghostfolio/client/services/data.service'; -import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { CreateAccountDto, UpdateAccountDto } from '@ghostfolio/common/dtos'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; 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 3aedb8d73..01b389789 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 @@ -7,6 +7,7 @@ import { LookupItem } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { translate } from '@ghostfolio/ui/i18n'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; @@ -48,7 +49,6 @@ import { EMPTY, Subject } from 'rxjs'; import { catchError, delay, takeUntil } from 'rxjs/operators'; import { DataService } from '../../../../services/data.service'; -import { validateObjectForForm } from '../../../../util/form.util'; import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; import { ActivityType } from './types/activity-type.type'; 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 a3d7d326d..582ab8e25 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 @@ -1,5 +1,3 @@ -import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; -import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; @@ -11,6 +9,8 @@ import { import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces'; import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; +import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; +import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { StepperOrientation, diff --git a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.type.ts b/libs/common/src/lib/enums/confirmation-dialog.type.ts similarity index 100% rename from apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.type.ts rename to libs/common/src/lib/enums/confirmation-dialog.type.ts diff --git a/libs/common/src/lib/enums/index.ts b/libs/common/src/lib/enums/index.ts new file mode 100644 index 000000000..7384741de --- /dev/null +++ b/libs/common/src/lib/enums/index.ts @@ -0,0 +1,4 @@ +import { ConfirmationDialogType } from './confirmation-dialog.type'; +import { SubscriptionType } from './subscription-type.type'; + +export { ConfirmationDialogType, SubscriptionType }; diff --git a/libs/common/src/lib/types/subscription-type.type.ts b/libs/common/src/lib/enums/subscription-type.type.ts similarity index 100% rename from libs/common/src/lib/types/subscription-type.type.ts rename to libs/common/src/lib/enums/subscription-type.type.ts diff --git a/libs/common/src/lib/interfaces/system-message.interface.ts b/libs/common/src/lib/interfaces/system-message.interface.ts index 253bd5a27..617d40ea2 100644 --- a/libs/common/src/lib/interfaces/system-message.interface.ts +++ b/libs/common/src/lib/interfaces/system-message.interface.ts @@ -1,4 +1,4 @@ -import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; +import { SubscriptionType } from '@ghostfolio/common/enums'; export interface SystemMessage { message: string; diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index 2e0906895..e60f01915 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -1,7 +1,5 @@ -import { - AccountWithPlatform, - SubscriptionType -} from '@ghostfolio/common/types'; +import { SubscriptionType } from '@ghostfolio/common/enums'; +import { AccountWithPlatform } from '@ghostfolio/common/types'; import { Access, Tag } from '@prisma/client'; diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index 903d9c96a..781e50c55 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -18,7 +18,6 @@ import type { Market } from './market.type'; import type { OrderWithAccount } from './order-with-account.type'; import type { RequestWithUser } from './request-with-user.type'; import type { SubscriptionOfferKey } from './subscription-offer-key.type'; -import type { SubscriptionType } from './subscription-type.type'; import type { UserWithSettings } from './user-with-settings.type'; import type { ViewMode } from './view-mode.type'; @@ -43,7 +42,6 @@ export type { OrderWithAccount, RequestWithUser, SubscriptionOfferKey, - SubscriptionType, UserWithSettings, ViewMode }; diff --git a/libs/common/src/lib/types/user-with-settings.type.ts b/libs/common/src/lib/types/user-with-settings.type.ts index 18fc90a5c..3c6adfec0 100644 --- a/libs/common/src/lib/types/user-with-settings.type.ts +++ b/libs/common/src/lib/types/user-with-settings.type.ts @@ -1,5 +1,5 @@ +import { SubscriptionType } from '@ghostfolio/common/enums'; import { SubscriptionOffer, UserSettings } from '@ghostfolio/common/interfaces'; -import { SubscriptionType } from '@ghostfolio/common/types'; import { Access, Account, Settings, User } from '@prisma/client'; diff --git a/apps/client/src/app/util/form.util.ts b/libs/common/src/lib/utils/form.util.ts similarity index 100% rename from apps/client/src/app/util/form.util.ts rename to libs/common/src/lib/utils/form.util.ts diff --git a/libs/common/src/lib/utils/index.ts b/libs/common/src/lib/utils/index.ts new file mode 100644 index 000000000..2bdd03fdc --- /dev/null +++ b/libs/common/src/lib/utils/index.ts @@ -0,0 +1,3 @@ +import { validateObjectForForm } from './form.util'; + +export { validateObjectForForm }; diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index 679899af9..5fe47347e 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -1,10 +1,10 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; -import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { DATE_FORMAT, getLocale } from '@ghostfolio/common/helper'; import { AccountBalancesResponse } from '@ghostfolio/common/interfaces'; +import { validateObjectForForm } from '@ghostfolio/common/utils'; import { CUSTOM_ELEMENTS_SCHEMA, diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index b96905981..c0995c39a 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -1,6 +1,6 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale } from '@ghostfolio/common/helper'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index d7b13a7e8..476fca5fb 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -1,10 +1,10 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DEFAULT_PAGE_SIZE, TAG_ID_EXCLUDE_FROM_ANALYSIS } from '@ghostfolio/common/config'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale } from '@ghostfolio/common/helper'; import { Activity, diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts index bcac9c6b5..59c1e6e17 100644 --- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts @@ -1,12 +1,12 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; -import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { DataService } from '@ghostfolio/client/services/data.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails, LineChartItem } from '@ghostfolio/common/interfaces'; +import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; +import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { CUSTOM_ELEMENTS_SCHEMA, diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index 4c1ca97cd..5793300c1 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,6 +1,6 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; +import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, diff --git a/apps/client/src/app/components/dialog-footer/dialog-footer.component.html b/libs/ui/src/lib/dialog-footer/dialog-footer.component.html similarity index 100% rename from apps/client/src/app/components/dialog-footer/dialog-footer.component.html rename to libs/ui/src/lib/dialog-footer/dialog-footer.component.html diff --git a/apps/client/src/app/components/dialog-footer/dialog-footer.component.scss b/libs/ui/src/lib/dialog-footer/dialog-footer.component.scss similarity index 100% rename from apps/client/src/app/components/dialog-footer/dialog-footer.component.scss rename to libs/ui/src/lib/dialog-footer/dialog-footer.component.scss diff --git a/apps/client/src/app/components/dialog-footer/dialog-footer.component.ts b/libs/ui/src/lib/dialog-footer/dialog-footer.component.ts similarity index 100% rename from apps/client/src/app/components/dialog-footer/dialog-footer.component.ts rename to libs/ui/src/lib/dialog-footer/dialog-footer.component.ts diff --git a/libs/ui/src/lib/dialog-footer/index.ts b/libs/ui/src/lib/dialog-footer/index.ts new file mode 100644 index 000000000..822be3e98 --- /dev/null +++ b/libs/ui/src/lib/dialog-footer/index.ts @@ -0,0 +1 @@ +export * from './dialog-footer.component'; diff --git a/apps/client/src/app/components/dialog-header/dialog-header.component.html b/libs/ui/src/lib/dialog-header/dialog-header.component.html similarity index 100% rename from apps/client/src/app/components/dialog-header/dialog-header.component.html rename to libs/ui/src/lib/dialog-header/dialog-header.component.html diff --git a/apps/client/src/app/components/dialog-header/dialog-header.component.scss b/libs/ui/src/lib/dialog-header/dialog-header.component.scss similarity index 100% rename from apps/client/src/app/components/dialog-header/dialog-header.component.scss rename to libs/ui/src/lib/dialog-header/dialog-header.component.scss diff --git a/apps/client/src/app/components/dialog-header/dialog-header.component.ts b/libs/ui/src/lib/dialog-header/dialog-header.component.ts similarity index 100% rename from apps/client/src/app/components/dialog-header/dialog-header.component.ts rename to libs/ui/src/lib/dialog-header/dialog-header.component.ts diff --git a/libs/ui/src/lib/dialog-header/index.ts b/libs/ui/src/lib/dialog-header/index.ts new file mode 100644 index 000000000..9beb9d4ac --- /dev/null +++ b/libs/ui/src/lib/dialog-header/index.ts @@ -0,0 +1 @@ +export * from './dialog-header.component'; From d16eef5fae70c6da00f5c26de9fb2de002eac44c Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:50:18 -0600 Subject: [PATCH 028/157] Task/integrate OSS Gallery into logo carousel (#5959) * Integrate OSS Gallery * Update changelog --- CHANGELOG.md | 6 ++++++ .../src/assets/images/logo-oss-gallery.svg | 18 ++++++++++++++++++ .../logo-carousel/logo-carousel.component.scss | 4 ++++ .../logo-carousel/logo-carousel.component.ts | 7 +++++++ 4 files changed, 35 insertions(+) create mode 100644 apps/client/src/assets/images/logo-oss-gallery.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 029675b83..1b357dec6 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 + +- Added the _OSS Gallery_ logo to the logo carousel on the landing page + ## 2.217.1 - 2025-11-16 ### Added diff --git a/apps/client/src/assets/images/logo-oss-gallery.svg b/apps/client/src/assets/images/logo-oss-gallery.svg new file mode 100644 index 000000000..e7fc2ffa8 --- /dev/null +++ b/apps/client/src/assets/images/logo-oss-gallery.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss index 89a837195..352f52ee8 100644 --- a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss +++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss @@ -113,6 +113,10 @@ mask-image: url('/assets/images/logo-openalternative.svg'); } + &.logo-oss-gallery { + mask-image: url('/assets/images/logo-oss-gallery.svg'); + } + &.logo-privacy-tools { mask-image: url('/assets/images/logo-privacy-tools.svg'); } diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts b/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts index ea6344694..9c1a90809 100644 --- a/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts +++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts @@ -48,6 +48,13 @@ export class GfLogoCarouselComponent { title: 'OpenAlternative: Open Source Alternatives to Popular Software', url: 'https://openalternative.co' }, + { + className: 'logo-oss-gallery', + isMask: true, + name: 'OSS Gallery', + title: 'OSS Gallery: Discover the best open-source projects', + url: 'https://oss.gallery' + }, { className: 'logo-privacy-tools', isMask: true, From a2466ebe28508235bfb6fb2bb4aaafa51867fabd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:43:43 +0100 Subject: [PATCH 029/157] Task/upgrade yahoo-finance2 to version 3.10.1 (#5956) * Upgrade yahoo-finance2 to version 3.10.1 * Update changelog --- CHANGELOG.md | 4 ++++ package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b357dec6..edcb92bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the _OSS Gallery_ logo to the logo carousel on the landing page +### Changed + +- Upgraded `yahoo-finance2` from version `3.10.0` to `3.10.1` + ## 2.217.1 - 2025-11-16 ### Added diff --git a/package-lock.json b/package-lock.json index ac4a7c15a..198da4e48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,7 +90,7 @@ "tablemark": "4.1.0", "twitter-api-v2": "1.27.0", "uuid": "11.1.0", - "yahoo-finance2": "3.10.0", + "yahoo-finance2": "3.10.1", "zone.js": "0.15.1" }, "devDependencies": { @@ -42027,9 +42027,9 @@ } }, "node_modules/yahoo-finance2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.10.0.tgz", - "integrity": "sha512-0mnvefEAapMS6M3tnqLmQlyE2W38AQqByaTS09l2dawLaVU7NNc0hJ4qI4F3qi3C7MU+ZWAb8DFVKpW6Zsj0Nw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.10.1.tgz", + "integrity": "sha512-HATfcK24E8o9gmF/Mh8nL9EYuy45xBXeq7VInkd4ZeK3wBX0AwTQ3ktzjZXKvoGylPrQ3IKMbZl7t3lcbO8fQA==", "license": "MIT", "dependencies": { "@deno/shim-deno": "~0.18.0", diff --git a/package.json b/package.json index d493143ed..9965e4714 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "tablemark": "4.1.0", "twitter-api-v2": "1.27.0", "uuid": "11.1.0", - "yahoo-finance2": "3.10.0", + "yahoo-finance2": "3.10.1", "zone.js": "0.15.1" }, "devDependencies": { From d296e6bd2822a084d0811ec9a4f6f5dd561946a0 Mon Sep 17 00:00:00 2001 From: Johnson Towoju Date: Wed, 19 Nov 2025 17:35:43 +0100 Subject: [PATCH 030/157] Feature/extend menu in accounts table component (#5960) * Extend menu * Update changelog --- CHANGELOG.md | 1 + .../ui/src/lib/accounts-table/accounts-table.component.html | 6 ++++++ libs/ui/src/lib/accounts-table/accounts-table.component.ts | 6 ++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edcb92bad..3e6027a13 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 ### Added +- Extended the accounts table menu with a _View Details_ item - Added the _OSS Gallery_ logo to the logo carousel on the landing page ### Changed diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html index 805ffe77d..c5ebaa657 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -301,6 +301,12 @@ +
+ +
Registration Date
-
- -
Authentication
-
- Role -
@if (data.hasPermissionForSubscription) { From 1ca32315dc245203c4a378f986b38bcbc1c5f1ef Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:19:08 +0100 Subject: [PATCH 049/157] Task/upgrade color to version 5.0.3 (#5984) * Upgrade color to version 5.0.3 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 36 ++++++++++++++++++------------------ package.json | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c70f99b7..aa78ea5c0 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 - Eliminated `uuid` in favor of using `randomUUID` from `node:crypto` +- Upgraded `color` from version `5.0.0` to `5.0.3` ### Fixed diff --git a/package-lock.json b/package-lock.json index 993413fa9..a68a42262 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "cheerio": "1.0.0", "class-transformer": "0.5.1", "class-validator": "0.14.2", - "color": "5.0.0", + "color": "5.0.3", "countries-and-timezones": "3.8.0", "countries-list": "3.2.0", "countup.js": "2.9.0", @@ -18018,13 +18018,13 @@ "license": "MIT" }, "node_modules/color": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.0.tgz", - "integrity": "sha512-16BlyiuyLq3MLxpRWyOTiWsO3ii/eLQLJUQXBSNcxMBBSnyt1ee9YUdaozQp03ifwm5woztEZGDbk9RGVuCsdw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", "license": "MIT", "dependencies": { - "color-convert": "^3.0.1", - "color-string": "^2.0.0" + "color-convert": "^3.1.3", + "color-string": "^2.1.3" }, "engines": { "node": ">=18" @@ -18049,9 +18049,9 @@ "license": "MIT" }, "node_modules/color-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.0.1.tgz", - "integrity": "sha512-5z9FbYTZPAo8iKsNEqRNv+OlpBbDcoE+SY9GjLfDUHEfcNNV7tS9eSAlFHEaub/r5tBL9LtskAeq1l9SaoZ5tQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -18061,18 +18061,18 @@ } }, "node_modules/color-string/node_modules/color-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.0.tgz", - "integrity": "sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "engines": { "node": ">=12.20" } }, "node_modules/color/node_modules/color-convert": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.0.tgz", - "integrity": "sha512-TVoqAq8ZDIpK5lsQY874DDnu65CSsc9vzq0wLpNQ6UMBq81GSZocVazPiBbYGzngzBOIRahpkTzCLVe2at4MfA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", "license": "MIT", "dependencies": { "color-name": "^2.0.0" @@ -18082,9 +18082,9 @@ } }, "node_modules/color/node_modules/color-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.0.tgz", - "integrity": "sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "engines": { "node": ">=12.20" diff --git a/package.json b/package.json index 7f66f0edd..654acdf61 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "cheerio": "1.0.0", "class-transformer": "0.5.1", "class-validator": "0.14.2", - "color": "5.0.0", + "color": "5.0.3", "countries-and-timezones": "3.8.0", "countries-list": "3.2.0", "countup.js": "2.9.0", From f18301c89e45f4c2763406423ce8cbf579256d44 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 28 Nov 2025 20:44:06 +0100 Subject: [PATCH 050/157] Task/remove obsolete includeDrafts attribute in public controller (#5975) * Remove obsolete includeDrafts attribute --- apps/api/src/app/endpoints/public/public.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index b09ced4fb..b4ecd37ba 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -82,7 +82,6 @@ export class PublicController { ]); const { activities } = await this.orderService.getOrders({ - includeDrafts: false, sortColumn: 'date', sortDirection: 'desc', take: 10, From d341c4804abab279484f21909ab018a5761a8013 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Nov 2025 13:57:57 +0100 Subject: [PATCH 051/157] Feature/improve asset profile data gathering (#5997) * Improve asset profile data gathering * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/admin/admin.controller.ts | 4 +- apps/api/src/services/cron/cron.service.ts | 4 +- .../data-gathering/data-gathering.service.ts | 53 +++++++++++-------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa78ea5c0..5acae5a13 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 +- Restricted the asset profile data gathering on Sundays to only process outdated asset profiles - Eliminated `uuid` in favor of using `randomUUID` from `node:crypto` - Upgraded `color` from version `5.0.0` to `5.0.3` diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 8b5da4965..24467c732 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -93,7 +93,7 @@ export class AdminController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async gatherMax(): Promise { const assetProfileIdentifiers = - await this.dataGatheringService.getAllActiveAssetProfileIdentifiers(); + await this.dataGatheringService.getActiveAssetProfileIdentifiers(); await this.dataGatheringService.addJobsToQueue( assetProfileIdentifiers.map(({ dataSource, symbol }) => { @@ -120,7 +120,7 @@ export class AdminController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async gatherProfileData(): Promise { const assetProfileIdentifiers = - await this.dataGatheringService.getAllActiveAssetProfileIdentifiers(); + await this.dataGatheringService.getActiveAssetProfileIdentifiers(); await this.dataGatheringService.addJobsToQueue( assetProfileIdentifiers.map(({ dataSource, symbol }) => { diff --git a/apps/api/src/services/cron/cron.service.ts b/apps/api/src/services/cron/cron.service.ts index 88fcabce2..ee91a811e 100644 --- a/apps/api/src/services/cron/cron.service.ts +++ b/apps/api/src/services/cron/cron.service.ts @@ -59,7 +59,9 @@ export class CronService { public async runEverySundayAtTwelvePm() { if (await this.isDataGatheringEnabled()) { const assetProfileIdentifiers = - await this.dataGatheringService.getAllActiveAssetProfileIdentifiers(); + await this.dataGatheringService.getActiveAssetProfileIdentifiers({ + maxAge: '60 days' + }); await this.dataGatheringService.addJobsToQueue( assetProfileIdentifiers.map(({ dataSource, symbol }) => { diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts index c433f692f..cec63c3eb 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts @@ -28,8 +28,9 @@ import { InjectQueue } from '@nestjs/bull'; import { Inject, Injectable, Logger } from '@nestjs/common'; import { DataSource } from '@prisma/client'; import { JobOptions, Queue } from 'bull'; -import { format, min, subDays, subYears } from 'date-fns'; +import { format, min, subDays, subMilliseconds, subYears } from 'date-fns'; import { isEmpty } from 'lodash'; +import ms, { StringValue } from 'ms'; @Injectable() export class DataGatheringService { @@ -160,8 +161,7 @@ export class DataGatheringService { ); if (!assetProfileIdentifiers) { - assetProfileIdentifiers = - await this.getAllActiveAssetProfileIdentifiers(); + assetProfileIdentifiers = await this.getActiveAssetProfileIdentifiers(); } if (assetProfileIdentifiers.length <= 0) { @@ -301,29 +301,36 @@ export class DataGatheringService { ); } - public async getAllActiveAssetProfileIdentifiers(): Promise< - AssetProfileIdentifier[] - > { - const symbolProfiles = await this.prismaService.symbolProfile.findMany({ - orderBy: [{ symbol: 'asc' }], + /** + * Returns active asset profile identifiers + * + * @param {StringValue} maxAge - Optional. Specifies the maximum allowed age + * of a profile’s last update timestamp. Only asset profiles considered stale + * are returned. + */ + public async getActiveAssetProfileIdentifiers({ + maxAge + }: { + maxAge?: StringValue; + } = {}): Promise { + return this.prismaService.symbolProfile.findMany({ + orderBy: [{ symbol: 'asc' }, { dataSource: 'asc' }], + select: { + dataSource: true, + symbol: true + }, where: { - isActive: true + dataSource: { + notIn: ['MANUAL', 'RAPID_API'] + }, + isActive: true, + ...(maxAge && { + updatedAt: { + lt: subMilliseconds(new Date(), ms(maxAge)) + } + }) } }); - - return symbolProfiles - .filter(({ dataSource }) => { - return ( - dataSource !== DataSource.MANUAL && - dataSource !== DataSource.RAPID_API - ); - }) - .map(({ dataSource, symbol }) => { - return { - dataSource, - symbol - }; - }); } private async getAssetProfileIdentifiersWithCompleteMarketData(): Promise< From 9092669dd8586c4353620e907044a62e241483aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Sat, 29 Nov 2025 14:12:08 +0100 Subject: [PATCH 052/157] Task/remove Cypress setup (#5995) * Remove Cypress setup * Update changelog --- CHANGELOG.md | 1 + apps/client-e2e/.eslintrc.json | 20 - apps/client-e2e/cypress.json | 12 - apps/client-e2e/project.json | 22 - apps/client-e2e/src/fixtures/example.json | 4 - apps/client-e2e/src/integration/app.spec.ts | 13 - apps/client-e2e/src/plugins/index.js | 22 - apps/client-e2e/src/support/app.po.ts | 1 - apps/client-e2e/src/support/commands.ts | 31 -- apps/client-e2e/src/support/index.ts | 16 - apps/client-e2e/tsconfig.e2e.json | 10 - apps/client-e2e/tsconfig.json | 10 - apps/ui-e2e/cypress.json | 13 - apps/ui-e2e/eslint.config.cjs | 33 -- apps/ui-e2e/project.json | 28 -- apps/ui-e2e/src/fixtures/example.json | 4 - .../integration/value/value.component.spec.ts | 6 - apps/ui-e2e/src/plugins/index.js | 22 - apps/ui-e2e/src/support/commands.ts | 33 -- apps/ui-e2e/src/support/index.ts | 16 - apps/ui-e2e/tsconfig.json | 10 - package-lock.json | 402 +++++++++++++++--- package.json | 5 - 23 files changed, 339 insertions(+), 395 deletions(-) delete mode 100644 apps/client-e2e/.eslintrc.json delete mode 100644 apps/client-e2e/cypress.json delete mode 100644 apps/client-e2e/project.json delete mode 100644 apps/client-e2e/src/fixtures/example.json delete mode 100644 apps/client-e2e/src/integration/app.spec.ts delete mode 100644 apps/client-e2e/src/plugins/index.js delete mode 100644 apps/client-e2e/src/support/app.po.ts delete mode 100644 apps/client-e2e/src/support/commands.ts delete mode 100644 apps/client-e2e/src/support/index.ts delete mode 100644 apps/client-e2e/tsconfig.e2e.json delete mode 100644 apps/client-e2e/tsconfig.json delete mode 100644 apps/ui-e2e/cypress.json delete mode 100644 apps/ui-e2e/eslint.config.cjs delete mode 100644 apps/ui-e2e/project.json delete mode 100644 apps/ui-e2e/src/fixtures/example.json delete mode 100644 apps/ui-e2e/src/integration/value/value.component.spec.ts delete mode 100644 apps/ui-e2e/src/plugins/index.js delete mode 100644 apps/ui-e2e/src/support/commands.ts delete mode 100644 apps/ui-e2e/src/support/index.ts delete mode 100644 apps/ui-e2e/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 5acae5a13..4236570c3 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 - Restricted the asset profile data gathering on Sundays to only process outdated asset profiles +- Removed the _Cypress_ testing setup - Eliminated `uuid` in favor of using `randomUUID` from `node:crypto` - Upgraded `color` from version `5.0.0` to `5.0.3` diff --git a/apps/client-e2e/.eslintrc.json b/apps/client-e2e/.eslintrc.json deleted file mode 100644 index dbedf6bd4..000000000 --- a/apps/client-e2e/.eslintrc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "parserOptions": { - "project": ["apps/client-e2e/tsconfig.*?.json"] - }, - "rules": {} - }, - { - "files": ["src/plugins/index.js"], - "rules": { - "@typescript-eslint/no-var-requires": "off", - "no-undef": "off" - } - } - ] -} diff --git a/apps/client-e2e/cypress.json b/apps/client-e2e/cypress.json deleted file mode 100644 index a8219f0fe..000000000 --- a/apps/client-e2e/cypress.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "fileServerFolder": ".", - "fixturesFolder": "./src/fixtures", - "integrationFolder": "./src/integration", - "modifyObstructiveCode": false, - "pluginsFile": "./src/plugins/index", - "supportFile": "./src/support/index.ts", - "video": true, - "videosFolder": "../../dist/cypress/apps/client-e2e/videos", - "screenshotsFolder": "../../dist/cypress/apps/client-e2e/screenshots", - "chromeWebSecurity": false -} diff --git a/apps/client-e2e/project.json b/apps/client-e2e/project.json deleted file mode 100644 index 92e2f09ef..000000000 --- a/apps/client-e2e/project.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "client-e2e", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/client-e2e/src", - "projectType": "application", - "tags": [], - "implicitDependencies": ["client"], - "targets": { - "e2e": { - "executor": "@nx/cypress:cypress", - "options": { - "cypressConfig": "apps/client-e2e/cypress.json", - "devServerTarget": "client:serve" - }, - "configurations": { - "production": { - "devServerTarget": "client:serve:production" - } - } - } - } -} diff --git a/apps/client-e2e/src/fixtures/example.json b/apps/client-e2e/src/fixtures/example.json deleted file mode 100644 index 294cbed6c..000000000 --- a/apps/client-e2e/src/fixtures/example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io" -} diff --git a/apps/client-e2e/src/integration/app.spec.ts b/apps/client-e2e/src/integration/app.spec.ts deleted file mode 100644 index b194092d7..000000000 --- a/apps/client-e2e/src/integration/app.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { getGreeting } from '../support/app.po'; - -describe('client', () => { - beforeEach(() => cy.visit('/')); - - it('should display welcome message', () => { - // Custom command example, see `../support/commands.ts` file - cy.login('my-email@something.com', 'myPassword'); - - // Function helper example, see `../support/app.po.ts` file - getGreeting().contains('Welcome to client!'); - }); -}); diff --git a/apps/client-e2e/src/plugins/index.js b/apps/client-e2e/src/plugins/index.js deleted file mode 100644 index 63aa33cbe..000000000 --- a/apps/client-e2e/src/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -const { preprocessTypescript } = require('@nx/cypress/plugins/preprocessor'); - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - - // Preprocess Typescript file using Nx helper - on('file:preprocessor', preprocessTypescript(config)); -}; diff --git a/apps/client-e2e/src/support/app.po.ts b/apps/client-e2e/src/support/app.po.ts deleted file mode 100644 index 329342469..000000000 --- a/apps/client-e2e/src/support/app.po.ts +++ /dev/null @@ -1 +0,0 @@ -export const getGreeting = () => cy.get('h1'); diff --git a/apps/client-e2e/src/support/commands.ts b/apps/client-e2e/src/support/commands.ts deleted file mode 100644 index 36c834059..000000000 --- a/apps/client-e2e/src/support/commands.ts +++ /dev/null @@ -1,31 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** - -declare namespace Cypress { - interface Chainable { - login(email: string, password: string): void; - } -} -// -// -- This is a parent command -- -Cypress.Commands.add('login', (email, password) => { - console.log('Custom command example: Login', email, password); -}); -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/client-e2e/src/support/index.ts b/apps/client-e2e/src/support/index.ts deleted file mode 100644 index fad130159..000000000 --- a/apps/client-e2e/src/support/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** -// Import commands.js using ES2015 syntax: -import './commands'; diff --git a/apps/client-e2e/tsconfig.e2e.json b/apps/client-e2e/tsconfig.e2e.json deleted file mode 100644 index 9dc3660a7..000000000 --- a/apps/client-e2e/tsconfig.e2e.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "sourceMap": false, - "outDir": "../../dist/out-tsc", - "allowJs": true, - "types": ["cypress", "node"] - }, - "include": ["src/**/*.ts", "src/**/*.js"] -} diff --git a/apps/client-e2e/tsconfig.json b/apps/client-e2e/tsconfig.json deleted file mode 100644 index 08841a7f5..000000000 --- a/apps/client-e2e/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.e2e.json" - } - ] -} diff --git a/apps/ui-e2e/cypress.json b/apps/ui-e2e/cypress.json deleted file mode 100644 index 71520788e..000000000 --- a/apps/ui-e2e/cypress.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "fileServerFolder": ".", - "fixturesFolder": "./src/fixtures", - "integrationFolder": "./src/integration", - "modifyObstructiveCode": false, - "supportFile": "./src/support/index.ts", - "pluginsFile": "./src/plugins/index", - "video": true, - "videosFolder": "../../dist/cypress/apps/ui-e2e/videos", - "screenshotsFolder": "../../dist/cypress/apps/ui-e2e/screenshots", - "chromeWebSecurity": false, - "baseUrl": "http://localhost:4400" -} diff --git a/apps/ui-e2e/eslint.config.cjs b/apps/ui-e2e/eslint.config.cjs deleted file mode 100644 index 5e6707635..000000000 --- a/apps/ui-e2e/eslint.config.cjs +++ /dev/null @@ -1,33 +0,0 @@ -const { FlatCompat } = require('@eslint/eslintrc'); -const js = require('@eslint/js'); -const baseConfig = require('../../eslint.config.cjs'); - -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended -}); - -module.exports = [ - { - ignores: ['**/dist'] - }, - ...baseConfig, - ...compat.extends('plugin:cypress/recommended'), - { - files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], - // Override or add rules here - rules: {}, - languageOptions: { - parserOptions: { - project: ['apps/ui-e2e/tsconfig.json'] - } - } - }, - { - files: ['src/plugins/index.js'], - rules: { - '@typescript-eslint/no-var-requires': 'off', - 'no-undef': 'off' - } - } -]; diff --git a/apps/ui-e2e/project.json b/apps/ui-e2e/project.json deleted file mode 100644 index a5b4cf53a..000000000 --- a/apps/ui-e2e/project.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "ui-e2e", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/ui-e2e/src", - "projectType": "application", - "tags": [], - "implicitDependencies": ["ui"], - "targets": { - "e2e": { - "executor": "@nx/cypress:cypress", - "options": { - "cypressConfig": "apps/ui-e2e/cypress.json", - "devServerTarget": "ui:storybook" - }, - "configurations": { - "ci": { - "devServerTarget": "ui:storybook:ci" - } - } - }, - "lint": { - "executor": "@nx/eslint:lint", - "options": { - "lintFilePatterns": ["apps/ui-e2e/**/*.{js,ts}"] - } - } - } -} diff --git a/apps/ui-e2e/src/fixtures/example.json b/apps/ui-e2e/src/fixtures/example.json deleted file mode 100644 index 294cbed6c..000000000 --- a/apps/ui-e2e/src/fixtures/example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io" -} diff --git a/apps/ui-e2e/src/integration/value/value.component.spec.ts b/apps/ui-e2e/src/integration/value/value.component.spec.ts deleted file mode 100644 index 5b90784e7..000000000 --- a/apps/ui-e2e/src/integration/value/value.component.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -describe('ui', () => { - beforeEach(() => cy.visit('/iframe.html?id=valuecomponent--loading')); - it('should render the component', () => { - cy.get('gf-value').should('exist'); - }); -}); diff --git a/apps/ui-e2e/src/plugins/index.js b/apps/ui-e2e/src/plugins/index.js deleted file mode 100644 index 63aa33cbe..000000000 --- a/apps/ui-e2e/src/plugins/index.js +++ /dev/null @@ -1,22 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -const { preprocessTypescript } = require('@nx/cypress/plugins/preprocessor'); - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - - // Preprocess Typescript file using Nx helper - on('file:preprocessor', preprocessTypescript(config)); -}; diff --git a/apps/ui-e2e/src/support/commands.ts b/apps/ui-e2e/src/support/commands.ts deleted file mode 100644 index 310f1fa0e..000000000 --- a/apps/ui-e2e/src/support/commands.ts +++ /dev/null @@ -1,33 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace Cypress { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface Chainable { - login(email: string, password: string): void; - } -} -// -// -- This is a parent command -- -Cypress.Commands.add('login', (email, password) => { - console.log('Custom command example: Login', email, password); -}); -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/ui-e2e/src/support/index.ts b/apps/ui-e2e/src/support/index.ts deleted file mode 100644 index fad130159..000000000 --- a/apps/ui-e2e/src/support/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** -// Import commands.js using ES2015 syntax: -import './commands'; diff --git a/apps/ui-e2e/tsconfig.json b/apps/ui-e2e/tsconfig.json deleted file mode 100644 index c4f818ecd..000000000 --- a/apps/ui-e2e/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "sourceMap": false, - "outDir": "../../dist/out-tsc", - "allowJs": true, - "types": ["cypress", "node"] - }, - "include": ["src/**/*.ts", "src/**/*.js"] -} diff --git a/package-lock.json b/package-lock.json index a68a42262..f765b52fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,7 +109,6 @@ "@nestjs/schematics": "11.0.9", "@nestjs/testing": "11.1.8", "@nx/angular": "21.5.1", - "@nx/cypress": "21.5.1", "@nx/eslint-plugin": "21.5.1", "@nx/jest": "21.5.1", "@nx/js": "21.5.1", @@ -132,10 +131,8 @@ "@types/passport-google-oauth20": "2.0.16", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", - "cypress": "6.2.1", "eslint": "9.35.0", "eslint-config-prettier": "10.1.8", - "eslint-plugin-cypress": "4.2.0", "eslint-plugin-import": "2.32.0", "eslint-plugin-storybook": "9.1.5", "husky": "9.1.7", @@ -4699,6 +4696,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "engines": { "node": ">=0.1.90" } @@ -4733,6 +4731,8 @@ "integrity": "sha512-EDiBsVPWC27DDLEJCo+dpl9ODHhdrwU57ccr9tspwCdG2ni0QVkf6LF0FGbhfujcjPxnXLIwsaks4sOrwrA4Qw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chalk": "^1.1.3", "cli-cursor": "^1.0.2", @@ -4749,6 +4749,8 @@ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4759,6 +4761,8 @@ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4769,6 +4773,8 @@ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -4785,7 +4791,9 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@cypress/listr-verbose-renderer/node_modules/escape-string-regexp": { "version": "1.0.5", @@ -4793,6 +4801,8 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -4803,6 +4813,8 @@ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -4816,6 +4828,8 @@ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -4826,6 +4840,8 @@ "integrity": "sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -4856,6 +4872,8 @@ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -4871,6 +4889,8 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -4881,6 +4901,8 @@ "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" @@ -4892,6 +4914,8 @@ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "^2.1.1" } @@ -4901,7 +4925,9 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@date-fns/utc": { "version": "2.1.0", @@ -12824,6 +12850,8 @@ "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "any-observable": "^0.3.0" }, @@ -14551,14 +14579,18 @@ "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@types/sizzle": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@types/sockjs": { "version": "0.3.36", @@ -15900,6 +15932,8 @@ "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -15956,7 +15990,9 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/arg": { "version": "4.1.3", @@ -16146,6 +16182,8 @@ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "safer-buffer": "~2.1.0" } @@ -16170,6 +16208,8 @@ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -16291,6 +16331,8 @@ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": "*" } @@ -16300,7 +16342,9 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/axios": { "version": "1.11.0", @@ -16677,6 +16721,8 @@ "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "tweetnacl": "^0.14.3" } @@ -16686,7 +16732,9 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true, - "license": "Unlicense" + "license": "Unlicense", + "optional": true, + "peer": true }, "node_modules/beasties": { "version": "0.3.5", @@ -16857,14 +16905,18 @@ "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true, + "peer": true }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/body-parser": { "version": "2.2.0", @@ -17068,6 +17120,8 @@ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "*" } @@ -17313,6 +17367,8 @@ "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -17444,7 +17500,9 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true, + "peer": true }, "node_modules/chai": { "version": "5.2.1", @@ -17565,6 +17623,8 @@ "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -17722,7 +17782,9 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/citty": { "version": "0.1.6", @@ -17787,6 +17849,8 @@ "integrity": "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "restore-cursor": "^1.0.1" }, @@ -17813,6 +17877,8 @@ "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "string-width": "^4.2.0" }, @@ -17829,6 +17895,8 @@ "integrity": "sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "slice-ansi": "0.0.4", "string-width": "^1.0.1" @@ -17843,6 +17911,8 @@ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17853,6 +17923,8 @@ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "number-is-nan": "^1.0.0" }, @@ -17866,6 +17938,8 @@ "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -17881,6 +17955,8 @@ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -18006,6 +18082,8 @@ "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -18175,6 +18253,8 @@ "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4.0.0" } @@ -18254,6 +18334,8 @@ "node >= 0.8" ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -18266,7 +18348,9 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/concat-stream/node_modules/readable-stream": { "version": "2.3.8", @@ -18274,6 +18358,8 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -18289,7 +18375,9 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/concat-stream/node_modules/string_decoder": { "version": "1.1.1", @@ -18297,6 +18385,8 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -19712,6 +19802,8 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@cypress/listr-verbose-renderer": "^0.4.1", "@cypress/request": "^2.88.5", @@ -19765,6 +19857,8 @@ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -19782,6 +19876,8 @@ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 6" } @@ -20343,6 +20439,8 @@ "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "assert-plus": "^1.0.0" }, @@ -21020,6 +21118,8 @@ "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -21030,7 +21130,9 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -21086,6 +21188,8 @@ "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -21712,32 +21816,6 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint-plugin-cypress": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-4.2.0.tgz", - "integrity": "sha512-v5cyt0VYb1tEEODBJSE44PocYOwQsckyexJhCs7LtdD3FGO6D2GjnZB2s2Sts4RcxdxECTWX01nObOZRs26bQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "globals": "^15.11.0" - }, - "peerDependencies": { - "eslint": ">=9" - } - }, - "node_modules/eslint-plugin-cypress/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-plugin-import": { "version": "2.32.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", @@ -22128,6 +22206,8 @@ "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -22151,7 +22231,9 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/executable": { "version": "4.1.1", @@ -22159,6 +22241,8 @@ "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pify": "^2.2.0" }, @@ -22181,6 +22265,8 @@ "integrity": "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -22468,6 +22554,8 @@ "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", "dev": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "dependencies": { "concat-stream": "^1.6.2", "debug": "^2.6.9", @@ -22484,6 +22572,8 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -22493,7 +22583,9 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/extsprintf": { "version": "1.3.0", @@ -22503,7 +22595,9 @@ "engines": [ "node >=0.6.0" ], - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/fast-check": { "version": "3.23.2", @@ -22661,6 +22755,8 @@ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pend": "~1.2.0" } @@ -22704,6 +22800,8 @@ "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5", "object-assign": "^4.1.0" @@ -22718,6 +22816,8 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -23034,6 +23134,8 @@ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": "*" } @@ -23583,6 +23685,8 @@ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pump": "^3.0.0" }, @@ -23624,6 +23728,8 @@ "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "async": "^3.2.0" } @@ -23634,6 +23740,8 @@ "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "assert-plus": "^1.0.0" } @@ -23745,6 +23853,8 @@ "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ini": "1.3.7" }, @@ -23760,7 +23870,9 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/global-modules": { "version": "1.0.0", @@ -24041,6 +24153,8 @@ "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -24054,6 +24168,8 @@ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -24610,6 +24726,8 @@ "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", @@ -24644,6 +24762,8 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "engines": { "node": ">=8.12.0" } @@ -24865,6 +24985,8 @@ "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -25108,6 +25230,8 @@ "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ci-info": "^2.0.0" }, @@ -25284,6 +25408,8 @@ "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "global-dirs": "^2.0.1", "is-path-inside": "^3.0.1" @@ -25379,6 +25505,8 @@ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "symbol-observable": "^1.1.0" }, @@ -25392,6 +25520,8 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -25431,7 +25561,9 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/is-regex": { "version": "1.2.1", @@ -25549,7 +25681,9 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -25704,7 +25838,9 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -30141,7 +30277,9 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/json5": { "version": "2.2.3", @@ -30331,6 +30469,8 @@ "node >=0.6.0" ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -30637,6 +30777,8 @@ "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "> 0.8" } @@ -30816,6 +30958,8 @@ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@samverschueren/stream-to-observable": "^0.3.0", "is-observable": "^1.1.0", @@ -30837,6 +30981,8 @@ "integrity": "sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -30847,6 +30993,8 @@ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chalk": "^1.1.3", "cli-truncate": "^0.2.1", @@ -30870,6 +31018,8 @@ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -30880,6 +31030,8 @@ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -30890,6 +31042,8 @@ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -30907,6 +31061,8 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -30917,6 +31073,8 @@ "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chalk": "^1.0.0" }, @@ -30930,6 +31088,8 @@ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -30943,6 +31103,8 @@ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -30953,6 +31115,8 @@ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chalk": "^2.4.1", "cli-cursor": "^2.1.0", @@ -30969,6 +31133,8 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -30982,6 +31148,8 @@ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -30997,6 +31165,8 @@ "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "restore-cursor": "^2.0.0" }, @@ -31010,6 +31180,8 @@ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -31019,14 +31191,18 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/listr-verbose-renderer/node_modules/date-fns": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/listr-verbose-renderer/node_modules/escape-string-regexp": { "version": "1.0.5", @@ -31034,6 +31210,8 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -31044,6 +31222,8 @@ "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -31057,6 +31237,8 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -31067,6 +31249,8 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -31077,6 +31261,8 @@ "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "mimic-fn": "^1.0.0" }, @@ -31090,6 +31276,8 @@ "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -31103,7 +31291,9 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/listr-verbose-renderer/node_modules/supports-color": { "version": "5.5.0", @@ -31111,6 +31301,8 @@ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -31124,6 +31316,8 @@ "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -31134,6 +31328,8 @@ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "tslib": "^1.9.0" }, @@ -31146,7 +31342,9 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "optional": true, + "peer": true }, "node_modules/listr2": { "version": "9.0.1", @@ -31647,6 +31845,8 @@ "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-escapes": "^3.0.0", "cli-cursor": "^2.0.0", @@ -31662,6 +31862,8 @@ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -31672,6 +31874,8 @@ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -31682,6 +31886,8 @@ "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "restore-cursor": "^2.0.0" }, @@ -31695,6 +31901,8 @@ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -31705,6 +31913,8 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -31715,6 +31925,8 @@ "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "mimic-fn": "^1.0.0" }, @@ -31728,6 +31940,8 @@ "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -31741,7 +31955,9 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "optional": true, + "peer": true }, "node_modules/log-update/node_modules/string-width": { "version": "2.1.1", @@ -31749,6 +31965,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -31763,6 +31981,8 @@ "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "ansi-regex": "^3.0.0" }, @@ -31776,6 +31996,8 @@ "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0" @@ -32393,6 +32615,8 @@ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "*" } @@ -33147,6 +33371,8 @@ "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -34020,7 +34246,9 @@ "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/own-keys": { "version": "1.0.1", @@ -34107,6 +34335,8 @@ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -34616,7 +34846,9 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/perfect-debounce": { "version": "1.0.0", @@ -34630,7 +34862,9 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/picocolors": { "version": "1.1.1", @@ -35549,6 +35783,8 @@ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" }, @@ -35751,6 +35987,8 @@ "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -35872,7 +36110,9 @@ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/randombytes": { "version": "2.1.0", @@ -36385,6 +36625,8 @@ "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "throttleit": "^1.0.0" } @@ -36395,6 +36637,8 @@ "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -36550,6 +36794,8 @@ "integrity": "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "exit-hook": "^1.0.0", "onetime": "^1.0.0" @@ -36564,6 +36810,8 @@ "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -37938,6 +38186,8 @@ "integrity": "sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -38151,6 +38401,8 @@ "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -38176,14 +38428,18 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/sshpk/node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true, - "license": "Unlicense" + "license": "Unlicense", + "optional": true, + "peer": true }, "node_modules/ssri": { "version": "12.0.0", @@ -38856,6 +39112,8 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -40026,6 +40284,8 @@ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -40446,6 +40706,8 @@ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -40507,6 +40769,8 @@ "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "punycode": "^1.4.1", "qs": "^6.12.3" @@ -40537,7 +40801,9 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/url/node_modules/qs": { "version": "6.14.0", @@ -40545,6 +40811,8 @@ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, "license": "BSD-3-Clause", + "optional": true, + "peer": true, "dependencies": { "side-channel": "^1.1.0" }, @@ -40684,6 +40952,8 @@ "node >=0.6.0" ], "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -40695,7 +40965,9 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/vite": { "version": "7.1.5", @@ -42211,6 +42483,8 @@ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/package.json b/package.json index 654acdf61..85b1ed48b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "affected:apps": "nx affected:apps", "affected:build": "nx affected:build", "affected:dep-graph": "nx affected:dep-graph", - "affected:e2e": "nx affected:e2e", "affected:libs": "nx affected:libs", "affected:lint": "nx affected:lint", "affected:test": "nx affected:test", @@ -27,7 +26,6 @@ "database:setup": "npm run database:push && npm run database:seed", "database:validate-schema": "prisma validate", "dep-graph": "nx dep-graph", - "e2e": "ng e2e", "extract-locales": "nx run client:extract-i18n --output-path ./apps/client/src/locales", "format": "nx format:write", "format:check": "nx format:check", @@ -155,7 +153,6 @@ "@nestjs/schematics": "11.0.9", "@nestjs/testing": "11.1.8", "@nx/angular": "21.5.1", - "@nx/cypress": "21.5.1", "@nx/eslint-plugin": "21.5.1", "@nx/jest": "21.5.1", "@nx/js": "21.5.1", @@ -178,10 +175,8 @@ "@types/passport-google-oauth20": "2.0.16", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", - "cypress": "6.2.1", "eslint": "9.35.0", "eslint-config-prettier": "10.1.8", - "eslint-plugin-cypress": "4.2.0", "eslint-plugin-import": "2.32.0", "eslint-plugin-storybook": "9.1.5", "husky": "9.1.7", From 1d011747c729607a21eabe70ba7ac691e67d05fa Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:54:05 +0100 Subject: [PATCH 053/157] Task/improve usability of actions in various tables (#5992) * Improve usability of actions --- .../access-table/access-table.component.html | 2 +- .../app/components/admin-jobs/admin-jobs.html | 6 ++++-- .../admin-market-data/admin-market-data.html | 2 +- .../admin-platform.component.html | 2 +- .../admin-tag/admin-tag.component.html | 2 +- .../components/admin-users/admin-users.html | 4 +++- .../accounts-table.component.html | 6 +++--- .../activities-table.component.html | 18 +++++++++++------- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/apps/client/src/app/components/access-table/access-table.component.html b/apps/client/src/app/components/access-table/access-table.component.html index abeda6de8..cb41904d3 100644 --- a/apps/client/src/app/components/access-table/access-table.component.html +++ b/apps/client/src/app/components/access-table/access-table.component.html @@ -73,7 +73,7 @@ } diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.html b/apps/client/src/app/components/admin-jobs/admin-jobs.html index 14f1b211b..a82294001 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.html +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.html @@ -205,14 +205,16 @@

diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.html b/apps/client/src/app/components/admin-tag/admin-tag.component.html index 8b1b510d7..86377c937 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.html +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -64,7 +64,7 @@
diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index e802e3272..eb63f8aa6 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -222,7 +222,9 @@ > - View Details + View Details... @if (hasPermissionToImpersonateAllUsers) { diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html index c5ebaa657..d127b4bf3 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -7,7 +7,7 @@ (click)="onTransferBalance()" > - Transfer Cash Balance... + Transfer Cash Balance...
} @@ -304,13 +304,13 @@
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index e9bebaa16..b8e1882d4 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -6,7 +6,7 @@ (click)="onImport()" > - Import Activities... + Import Activities... @if (hasPermissionToExportActivities) { @if (hasPermissionToExportActivities) { @@ -379,7 +379,9 @@ > - Import Activities... + Import Activities... } @@ -391,7 +393,9 @@ > - Import Dividends... + Import Dividends... } @@ -443,20 +447,20 @@ }
-
-
-
- } -
-
-
Membership
-
{{ name }}
+
+ diff --git a/libs/ui/src/lib/membership-card/membership-card.component.scss b/libs/ui/src/lib/membership-card/membership-card.component.scss index 270adc0f1..fcd923f12 100644 --- a/libs/ui/src/lib/membership-card/membership-card.component.scss +++ b/libs/ui/src/lib/membership-card/membership-card.component.scss @@ -1,71 +1,231 @@ :host { --borderRadius: 1rem; --borderWidth: 2px; + --hover3dSpotlightOpacity: 0.2; display: block; max-width: 25rem; padding-top: calc(1 * var(--borderWidth)); width: 100%; - .card-container { - border-radius: var(--borderRadius); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15); + .card-wrapper { + &.hover-3d { + perspective: 1000px; + } - &:after { - animation: animatedborder 7s ease alternate infinite; - background: linear-gradient(60deg, #5073b8, #1098ad, #07b39b, #6fba82); - background-size: 300% 300%; + .card-container { border-radius: var(--borderRadius); - content: ''; - height: calc(100% + var(--borderWidth) * 2); - left: calc(-1 * var(--borderWidth)); - top: calc(-1 * var(--borderWidth)); - position: absolute; - width: calc(100% + var(--borderWidth) * 2); - z-index: -1; - - @keyframes animatedborder { - 0% { - background-position: 0% 50%; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15); + + .card-item { + aspect-ratio: 1.586; + background-color: #1d2124; + border-radius: calc(var(--borderRadius) - var(--borderWidth)); + color: rgba(var(--light-primary-text)); + line-height: 1.2; + + button { + color: rgba(var(--light-primary-text)); + height: 1.5rem; + z-index: 3; } - 50% { - background-position: 100% 50%; + + .heading { + font-size: 13px; } - 100% { - background-position: 0% 50%; + + .value { + font-size: 18px; + } + } + + &:not(.premium) { + &::after { + opacity: 0; + } + + .card-item { + background-color: #ffffff; + color: rgba(var(--dark-primary-text)); } } } - .card-item { - aspect-ratio: 1.586; - background-color: #1d2124; - border-radius: calc(var(--borderRadius) - var(--borderWidth)); - color: rgba(var(--light-primary-text)); - line-height: 1.2; + &.hover-3d { + --hover3d-rotate-x: 0; + --hover3d-rotate-y: 0; + --hover3d-shine: 100% 100%; - button { - color: rgba(var(--light-primary-text)); - height: 1.5rem; + .card-container { + overflow: hidden; + position: relative; + scale: 1; + transform: rotate3d( + var(--hover3d-rotate-x), + var(--hover3d-rotate-y), + 0, + 10deg + ); + transform-style: preserve-3d; + transition: + box-shadow 400ms ease-out, + scale 500ms ease-out, + transform 500ms ease-out; + will-change: transform, scale; + + &::before { + background-image: radial-gradient( + circle at 50%, + rgba(255, 255, 255, var(--hover3dSpotlightOpacity)) 10%, + transparent 50% + ); + content: ''; + filter: blur(0.75rem); + height: 33.333%; + opacity: 0; + pointer-events: none; + position: absolute; + scale: 500%; + translate: var(--hover3d-shine); + transition: + opacity 400ms ease-out, + translate 400ms ease-out; + width: 33.333%; + z-index: 1; + } + + .card-item { + position: relative; + + .hover-zone { + height: 33.333%; + width: 33.333%; + z-index: 2; + + &:nth-child(1) { + left: 0; + top: 0; + } + + &:nth-child(2) { + left: 33.333%; + top: 0; + } + + &:nth-child(3) { + right: 0; + top: 0; + } + + &:nth-child(4) { + left: 0; + top: 33.333%; + } + + &:nth-child(5) { + left: 33.333%; + top: 33.333%; + } + + &:nth-child(6) { + right: 0; + top: 33.333%; + } + + &:nth-child(7) { + bottom: 0; + left: 0; + } + + &:nth-child(8) { + bottom: 0; + left: 33.333%; + } + + &:nth-child(9) { + bottom: 0; + right: 0; + } + } + } } - .heading { - font-size: 13px; + &:has(.hover-zone:hover) .card-container { + box-shadow: 0 18px 40px rgba(15, 23, 42, 0.3); + scale: 1.05; + + &::before { + opacity: 1; + } } - .value { - font-size: 18px; + &:has(.hover-zone:nth-child(1):hover) { + --hover3d-rotate-x: 1; + --hover3d-rotate-y: -1; + --hover3d-shine: 0% 0%; } - } - &:not(.premium) { - &:after { - opacity: 0; + &:has(.hover-zone:nth-child(2):hover) { + --hover3d-rotate-x: 1; + --hover3d-rotate-y: 0; + --hover3d-shine: 100% 0%; } - .card-item { - background-color: #ffffff; - color: rgba(var(--dark-primary-text)); + &:has(.hover-zone:nth-child(3):hover) { + --hover3d-rotate-x: 1; + --hover3d-rotate-y: 1; + --hover3d-shine: 200% 0%; + } + + &:has(.hover-zone:nth-child(4):hover) { + --hover3d-rotate-x: 0; + --hover3d-rotate-y: -1; + --hover3d-shine: 0% 100%; + } + + &:has(.hover-zone:nth-child(5):hover) { + --hover3d-rotate-x: 0; + --hover3d-rotate-y: 0; + --hover3d-shine: 100% 100%; + } + + &:has(.hover-zone:nth-child(6):hover) { + --hover3d-rotate-x: 0; + --hover3d-rotate-y: 1; + --hover3d-shine: 200% 100%; + } + + &:has(.hover-zone:nth-child(7):hover) { + --hover3d-rotate-x: -1; + --hover3d-rotate-y: -1; + --hover3d-shine: 0% 200%; + } + + &:has(.hover-zone:nth-child(8):hover) { + --hover3d-rotate-x: -1; + --hover3d-rotate-y: 0; + --hover3d-shine: 100% 200%; + } + + &:has(.hover-zone:nth-child(9):hover) { + --hover3d-rotate-x: -1; + --hover3d-rotate-y: 1; + --hover3d-shine: 200% 200%; + } + } + } + + @media (prefers-reduced-motion: reduce) { + .card-wrapper.hover-3d { + .card-container { + scale: 1 !important; + transform: none !important; + transition: none !important; + + &::before { + opacity: 0 !important; + transition: none !important; + } } } } diff --git a/libs/ui/src/lib/membership-card/membership-card.component.stories.ts b/libs/ui/src/lib/membership-card/membership-card.component.stories.ts index 6b6fbe038..0d475bda7 100644 --- a/libs/ui/src/lib/membership-card/membership-card.component.stories.ts +++ b/libs/ui/src/lib/membership-card/membership-card.component.stories.ts @@ -26,6 +26,9 @@ export default { }) ], argTypes: { + hover3d: { + control: { type: 'boolean' } + }, name: { control: { type: 'select' }, options: ['Basic', 'Premium'] @@ -37,6 +40,7 @@ type Story = StoryObj; export const Basic: Story = { args: { + hover3d: false, name: 'Basic' } }; @@ -45,6 +49,7 @@ export const Premium: Story = { args: { expiresAt: addYears(new Date(), 1).toLocaleDateString(), hasPermissionToCreateApiKey: true, + hover3d: false, name: 'Premium' } }; diff --git a/libs/ui/src/lib/membership-card/membership-card.component.ts b/libs/ui/src/lib/membership-card/membership-card.component.ts index 175a94f42..be223758d 100644 --- a/libs/ui/src/lib/membership-card/membership-card.component.ts +++ b/libs/ui/src/lib/membership-card/membership-card.component.ts @@ -34,6 +34,7 @@ import { GfLogoComponent } from '../logo'; export class GfMembershipCardComponent { @Input() public expiresAt: string; @Input() public hasPermissionToCreateApiKey: boolean; + @Input() public hover3d = false; @Input() public name: string; @Output() generateApiKeyClicked = new EventEmitter(); From 4cd16c33f884a5a9dc2e98e56b85cf83a4b1e7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Sun, 7 Dec 2025 10:04:20 +0100 Subject: [PATCH 074/157] Feature/OIDC authentication (#5981) * Set up OIDC authentication * Update changelog --- CHANGELOG.md | 5 +- apps/api/src/app/auth/auth.controller.ts | 48 ++++++-- apps/api/src/app/auth/auth.module.ts | 82 ++++++++++++- apps/api/src/app/auth/auth.service.ts | 36 +++--- .../api/src/app/auth/interfaces/interfaces.ts | 19 +++ apps/api/src/app/auth/oidc-state.store.ts | 114 ++++++++++++++++++ apps/api/src/app/auth/oidc.strategy.ts | 69 +++++++++++ apps/api/src/app/info/info.service.ts | 4 + .../configuration/configuration.service.ts | 26 +++- .../interfaces/environment.interface.ts | 9 ++ .../app/components/header/header.component.ts | 7 ++ .../interfaces/interfaces.ts | 1 + .../login-with-access-token-dialog.html | 11 ++ libs/common/src/lib/permissions.ts | 2 + package-lock.json | 43 +++++++ package.json | 2 + .../migration.sql | 2 + prisma/schema.prisma | 1 + 18 files changed, 447 insertions(+), 34 deletions(-) create mode 100644 apps/api/src/app/auth/oidc-state.store.ts create mode 100644 apps/api/src/app/auth/oidc.strategy.ts create mode 100644 prisma/migrations/20251103162035_added_oidc_to_provider/migration.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 878d90326..7b3f93910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -#### Added +### Added - Introduced data source transformation support in the import functionality for self-hosted environments +- Added _OpenID Connect_ (`OIDC`) as a new login provider for self-hosted environments (experimental) - Added an optional 3D hover effect to the membership card component -#### Changed +### Changed - Increased the numerical precision for cryptocurrency quantities in the holding detail dialog - Upgraded `envalid` from version `8.1.0` to `8.1.1` diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index b45e7b97b..388f1dbd3 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -84,7 +84,6 @@ export class AuthController { @Req() request: Request, @Res() response: Response ) { - // Handles the Google OAuth2 callback const jwt: string = (request.user as any).jwt; if (jwt) { @@ -102,6 +101,46 @@ export class AuthController { } } + @Get('oidc') + @UseGuards(AuthGuard('oidc')) + @Version(VERSION_NEUTRAL) + public oidcLogin() { + if (!this.configurationService.get('ENABLE_FEATURE_AUTH_OIDC')) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + } + + @Get('oidc/callback') + @UseGuards(AuthGuard('oidc')) + @Version(VERSION_NEUTRAL) + public oidcLoginCallback(@Req() request: Request, @Res() response: Response) { + const jwt: string = (request.user as any).jwt; + + if (jwt) { + response.redirect( + `${this.configurationService.get( + 'ROOT_URL' + )}/${DEFAULT_LANGUAGE_CODE}/auth/${jwt}` + ); + } else { + response.redirect( + `${this.configurationService.get( + 'ROOT_URL' + )}/${DEFAULT_LANGUAGE_CODE}/auth` + ); + } + } + + @Post('webauthn/generate-authentication-options') + public async generateAuthenticationOptions( + @Body() body: { deviceId: string } + ) { + return this.webAuthService.generateAuthenticationOptions(body.deviceId); + } + @Get('webauthn/generate-registration-options') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async generateRegistrationOptions() { @@ -116,13 +155,6 @@ export class AuthController { return this.webAuthService.verifyAttestation(body.credential); } - @Post('webauthn/generate-authentication-options') - public async generateAuthenticationOptions( - @Body() body: { deviceId: string } - ) { - return this.webAuthService.generateAuthenticationOptions(body.deviceId); - } - @Post('webauthn/verify-authentication') public async verifyAuthentication( @Body() body: { deviceId: string; credential: AssertionCredentialJSON } diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts index 824c432b1..9fc5d0925 100644 --- a/apps/api/src/app/auth/auth.module.ts +++ b/apps/api/src/app/auth/auth.module.ts @@ -4,17 +4,20 @@ import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscriptio import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; -import { Module } from '@nestjs/common'; +import { Logger, Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; +import type { StrategyOptions } from 'passport-openidconnect'; import { ApiKeyStrategy } from './api-key.strategy'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { GoogleStrategy } from './google.strategy'; import { JwtStrategy } from './jwt.strategy'; +import { OidcStrategy } from './oidc.strategy'; @Module({ controllers: [AuthController], @@ -36,6 +39,83 @@ import { JwtStrategy } from './jwt.strategy'; AuthService, GoogleStrategy, JwtStrategy, + { + inject: [AuthService, ConfigurationService], + provide: OidcStrategy, + useFactory: async ( + authService: AuthService, + configurationService: ConfigurationService + ) => { + const isOidcEnabled = configurationService.get( + 'ENABLE_FEATURE_AUTH_OIDC' + ); + + if (!isOidcEnabled) { + return null; + } + + const issuer = configurationService.get('OIDC_ISSUER'); + const scope = configurationService.get('OIDC_SCOPE'); + + const callbackUrl = + configurationService.get('OIDC_CALLBACK_URL') || + `${configurationService.get('ROOT_URL')}/api/auth/oidc/callback`; + + // Check for manual URL overrides + const manualAuthorizationUrl = configurationService.get( + 'OIDC_AUTHORIZATION_URL' + ); + const manualTokenUrl = configurationService.get('OIDC_TOKEN_URL'); + const manualUserInfoUrl = + configurationService.get('OIDC_USER_INFO_URL'); + + let authorizationURL: string; + let tokenURL: string; + let userInfoURL: string; + + if (manualAuthorizationUrl && manualTokenUrl && manualUserInfoUrl) { + // Use manual URLs + authorizationURL = manualAuthorizationUrl; + tokenURL = manualTokenUrl; + userInfoURL = manualUserInfoUrl; + } else { + // Fetch OIDC configuration from discovery endpoint + try { + const response = await fetch( + `${issuer}/.well-known/openid-configuration` + ); + + const config = (await response.json()) as { + authorization_endpoint: string; + token_endpoint: string; + userinfo_endpoint: string; + }; + + // Manual URLs take priority over discovered ones + authorizationURL = + manualAuthorizationUrl || config.authorization_endpoint; + tokenURL = manualTokenUrl || config.token_endpoint; + userInfoURL = manualUserInfoUrl || config.userinfo_endpoint; + } catch (error) { + Logger.error(error, 'OidcStrategy'); + throw new Error('Failed to fetch OIDC configuration from issuer'); + } + } + + const options: StrategyOptions = { + authorizationURL, + issuer, + scope, + tokenURL, + userInfoURL, + callbackURL: callbackUrl, + clientID: configurationService.get('OIDC_CLIENT_ID'), + clientSecret: configurationService.get('OIDC_CLIENT_SECRET') + }; + + return new OidcStrategy(authService, options); + } + }, WebAuthService ] }) diff --git a/apps/api/src/app/auth/auth.service.ts b/apps/api/src/app/auth/auth.service.ts index a6ee5d260..6fe50dce0 100644 --- a/apps/api/src/app/auth/auth.service.ts +++ b/apps/api/src/app/auth/auth.service.ts @@ -17,30 +17,22 @@ export class AuthService { ) {} public async validateAnonymousLogin(accessToken: string): Promise { - return new Promise(async (resolve, reject) => { - try { - const hashedAccessToken = this.userService.createAccessToken({ - password: accessToken, - salt: this.configurationService.get('ACCESS_TOKEN_SALT') - }); + const hashedAccessToken = this.userService.createAccessToken({ + password: accessToken, + salt: this.configurationService.get('ACCESS_TOKEN_SALT') + }); - const [user] = await this.userService.users({ - where: { accessToken: hashedAccessToken } - }); + const [user] = await this.userService.users({ + where: { accessToken: hashedAccessToken } + }); - if (user) { - const jwt = this.jwtService.sign({ - id: user.id - }); + if (user) { + return this.jwtService.sign({ + id: user.id + }); + } - resolve(jwt); - } else { - throw new Error(); - } - } catch { - reject(); - } - }); + throw new Error(); } public async validateOAuthLogin({ @@ -75,7 +67,7 @@ export class AuthService { } catch (error) { throw new InternalServerErrorException( 'validateOAuthLogin', - error.message + error instanceof Error ? error.message : 'Unknown error' ); } } diff --git a/apps/api/src/app/auth/interfaces/interfaces.ts b/apps/api/src/app/auth/interfaces/interfaces.ts index 4fdcc25b5..7ddfe41d2 100644 --- a/apps/api/src/app/auth/interfaces/interfaces.ts +++ b/apps/api/src/app/auth/interfaces/interfaces.ts @@ -6,6 +6,25 @@ export interface AuthDeviceDialogParams { authDevice: AuthDeviceDto; } +export interface OidcContext { + claims?: { + sub?: string; + }; +} + +export interface OidcIdToken { + sub?: string; +} + +export interface OidcParams { + sub?: string; +} + +export interface OidcProfile { + id?: string; + sub?: string; +} + export interface ValidateOAuthLoginParams { provider: Provider; thirdPartyId: string; diff --git a/apps/api/src/app/auth/oidc-state.store.ts b/apps/api/src/app/auth/oidc-state.store.ts new file mode 100644 index 000000000..653451166 --- /dev/null +++ b/apps/api/src/app/auth/oidc-state.store.ts @@ -0,0 +1,114 @@ +import ms from 'ms'; + +/** + * Custom state store for OIDC authentication that doesn't rely on express-session. + * This store manages OAuth2 state parameters in memory with automatic cleanup. + */ +export class OidcStateStore { + private readonly STATE_EXPIRY_MS = ms('10 minutes'); + + private stateMap = new Map< + string, + { + appState?: unknown; + ctx: { issued?: Date; maxAge?: number; nonce?: string }; + meta?: unknown; + timestamp: number; + } + >(); + + /** + * Store request state. + * Signature matches passport-openidconnect SessionStore + */ + public store( + _req: unknown, + _meta: unknown, + appState: unknown, + ctx: { maxAge?: number; nonce?: string; issued?: Date }, + callback: (err: Error | null, handle?: string) => void + ) { + try { + // Generate a unique handle for this state + const handle = this.generateHandle(); + + this.stateMap.set(handle, { + appState, + ctx, + meta: _meta, + timestamp: Date.now() + }); + + // Clean up expired states + this.cleanup(); + + callback(null, handle); + } catch (error) { + callback(error as Error); + } + } + + /** + * Verify request state. + * Signature matches passport-openidconnect SessionStore + */ + public verify( + _req: unknown, + handle: string, + callback: ( + err: Error | null, + appState?: unknown, + ctx?: { maxAge?: number; nonce?: string; issued?: Date } + ) => void + ) { + try { + const data = this.stateMap.get(handle); + + if (!data) { + return callback(null, undefined, undefined); + } + + if (Date.now() - data.timestamp > this.STATE_EXPIRY_MS) { + // State has expired + this.stateMap.delete(handle); + return callback(null, undefined, undefined); + } + + // Remove state after verification (one-time use) + this.stateMap.delete(handle); + + callback(null, data.ctx, data.appState); + } catch (error) { + callback(error as Error); + } + } + + /** + * Clean up expired states + */ + private cleanup() { + const now = Date.now(); + const expiredKeys: string[] = []; + + for (const [key, value] of this.stateMap.entries()) { + if (now - value.timestamp > this.STATE_EXPIRY_MS) { + expiredKeys.push(key); + } + } + + for (const key of expiredKeys) { + this.stateMap.delete(key); + } + } + + /** + * Generate a cryptographically secure random handle + */ + private generateHandle() { + return ( + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + + Date.now().toString(36) + ); + } +} diff --git a/apps/api/src/app/auth/oidc.strategy.ts b/apps/api/src/app/auth/oidc.strategy.ts new file mode 100644 index 000000000..96b284121 --- /dev/null +++ b/apps/api/src/app/auth/oidc.strategy.ts @@ -0,0 +1,69 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Provider } from '@prisma/client'; +import { Request } from 'express'; +import { Strategy, type StrategyOptions } from 'passport-openidconnect'; + +import { AuthService } from './auth.service'; +import { + OidcContext, + OidcIdToken, + OidcParams, + OidcProfile +} from './interfaces/interfaces'; +import { OidcStateStore } from './oidc-state.store'; + +@Injectable() +export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') { + private static readonly stateStore = new OidcStateStore(); + + public constructor( + private readonly authService: AuthService, + options: StrategyOptions + ) { + super({ + ...options, + passReqToCallback: true, + store: OidcStrategy.stateStore + }); + } + + public async validate( + _request: Request, + issuer: string, + profile: OidcProfile, + context: OidcContext, + idToken: OidcIdToken, + _accessToken: string, + _refreshToken: string, + params: OidcParams + ) { + try { + const thirdPartyId = + profile?.id ?? + profile?.sub ?? + idToken?.sub ?? + params?.sub ?? + context?.claims?.sub; + + const jwt = await this.authService.validateOAuthLogin({ + thirdPartyId, + provider: Provider.OIDC + }); + + if (!thirdPartyId) { + Logger.error( + `Missing subject identifier in OIDC response from ${issuer}`, + 'OidcStrategy' + ); + + throw new Error('Missing subject identifier in OIDC response'); + } + + return { jwt }; + } catch (error) { + Logger.error(error, 'OidcStrategy'); + throw error; + } + } +} diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 634fc959c..3802e3ef4 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -55,6 +55,10 @@ export class InfoService { globalPermissions.push(permissions.enableAuthGoogle); } + if (this.configurationService.get('ENABLE_FEATURE_AUTH_OIDC')) { + globalPermissions.push(permissions.enableAuthOidc); + } + if (this.configurationService.get('ENABLE_FEATURE_AUTH_TOKEN')) { globalPermissions.push(permissions.enableAuthToken); } diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index f37189569..a91aa6e69 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -41,6 +41,7 @@ export class ConfigurationService { default: [] }), ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }), + ENABLE_FEATURE_AUTH_OIDC: bool({ default: false }), ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: bool({ default: true }), @@ -54,9 +55,32 @@ export class ConfigurationService { GOOGLE_SHEETS_ID: str({ default: '' }), GOOGLE_SHEETS_PRIVATE_KEY: str({ default: '' }), HOST: host({ default: DEFAULT_HOST }), - JWT_SECRET_KEY: str({}), + JWT_SECRET_KEY: str(), MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }), MAX_CHART_ITEMS: num({ default: 365 }), + OIDC_AUTHORIZATION_URL: str({ default: '' }), + OIDC_CALLBACK_URL: str({ default: '' }), + OIDC_CLIENT_ID: str({ + default: undefined, + requiredWhen: (env) => { + return env.ENABLE_FEATURE_AUTH_OIDC === true; + } + }), + OIDC_CLIENT_SECRET: str({ + default: undefined, + requiredWhen: (env) => { + return env.ENABLE_FEATURE_AUTH_OIDC === true; + } + }), + OIDC_ISSUER: str({ + default: undefined, + requiredWhen: (env) => { + return env.ENABLE_FEATURE_AUTH_OIDC === true; + } + }), + OIDC_SCOPE: json({ default: ['openid'] }), + OIDC_TOKEN_URL: str({ default: '' }), + OIDC_USER_INFO_URL: str({ default: '' }), PORT: port({ default: DEFAULT_PORT }), PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY: num({ default: DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 3a2ac687c..3c03744f1 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -17,6 +17,7 @@ export interface Environment extends CleanedEnvAccessors { DATA_SOURCES: string[]; DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: string[]; ENABLE_FEATURE_AUTH_GOOGLE: boolean; + ENABLE_FEATURE_AUTH_OIDC: boolean; ENABLE_FEATURE_AUTH_TOKEN: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean; @@ -32,6 +33,14 @@ export interface Environment extends CleanedEnvAccessors { JWT_SECRET_KEY: string; MAX_ACTIVITIES_TO_IMPORT: number; MAX_CHART_ITEMS: number; + OIDC_AUTHORIZATION_URL: string; + OIDC_CALLBACK_URL: string; + OIDC_CLIENT_ID: string; + OIDC_CLIENT_SECRET: string; + OIDC_ISSUER: string; + OIDC_SCOPE: string[]; + OIDC_TOKEN_URL: string; + OIDC_USER_INFO_URL: string; PORT: number; PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY: number; PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY: number; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 9fb9a8351..80239d56f 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -105,6 +105,7 @@ export class GfHeaderComponent implements OnChanges { public hasFilters: boolean; public hasImpersonationId: boolean; public hasPermissionForAuthGoogle: boolean; + public hasPermissionForAuthOidc: boolean; public hasPermissionForAuthToken: boolean; public hasPermissionForSubscription: boolean; public hasPermissionToAccessAdminControl: boolean; @@ -170,6 +171,11 @@ export class GfHeaderComponent implements OnChanges { permissions.enableAuthGoogle ); + this.hasPermissionForAuthOidc = hasPermission( + this.info?.globalPermissions, + permissions.enableAuthOidc + ); + this.hasPermissionForAuthToken = hasPermission( this.info?.globalPermissions, permissions.enableAuthToken @@ -286,6 +292,7 @@ export class GfHeaderComponent implements OnChanges { data: { accessToken: '', hasPermissionToUseAuthGoogle: this.hasPermissionForAuthGoogle, + hasPermissionToUseAuthOidc: this.hasPermissionForAuthOidc, hasPermissionToUseAuthToken: this.hasPermissionForAuthToken, title: $localize`Sign in` }, diff --git a/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts index c7c4ab3fd..e9222e142 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts @@ -1,6 +1,7 @@ export interface LoginWithAccessTokenDialogParams { accessToken: string; hasPermissionToUseAuthGoogle: boolean; + hasPermissionToUseAuthOidc: boolean; hasPermissionToUseAuthToken: boolean; title: string; } diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html index bc232cfb7..cf5611ef7 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -45,6 +45,17 @@ >
} + + @if (data.hasPermissionToUseAuthOidc) { + + }
diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index 597c27690..2e244568c 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -29,6 +29,7 @@ export const permissions = { deleteUser: 'deleteUser', deleteWatchlistItem: 'deleteWatchlistItem', enableAuthGoogle: 'enableAuthGoogle', + enableAuthOidc: 'enableAuthOidc', enableAuthToken: 'enableAuthToken', enableDataProviderGhostfolio: 'enableDataProviderGhostfolio', enableFearAndGreedIndex: 'enableFearAndGreedIndex', @@ -159,6 +160,7 @@ export function filterGlobalPermissions( return globalPermissions.filter((permission) => { return ( permission !== permissions.enableAuthGoogle && + permission !== permissions.enableAuthOidc && permission !== permissions.enableSubscription ); }); diff --git a/package-lock.json b/package-lock.json index 6a3829a1d..faa39e722 100644 --- a/package-lock.json +++ b/package-lock.json @@ -83,6 +83,7 @@ "passport-google-oauth20": "2.0.0", "passport-headerapikey": "1.2.2", "passport-jwt": "4.0.1", + "passport-openidconnect": "0.1.2", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "18.5.0", @@ -129,6 +130,7 @@ "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", + "@types/passport-openidconnect": "0.1.3", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", "eslint": "9.35.0", @@ -14502,6 +14504,30 @@ "@types/passport": "*" } }, + "node_modules/@types/passport-openidconnect": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@types/passport-openidconnect/-/passport-openidconnect-0.1.3.tgz", + "integrity": "sha512-k1Ni7bG/9OZNo2Qpjg2W6GajL+pww6ZPaNWMXfpteCX4dXf4QgaZLt2hjR5IiPrqwBT9+W8KjCTJ/uhGIoBx/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -34723,6 +34749,23 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-openidconnect": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.2.tgz", + "integrity": "sha512-JX3rTyW+KFZ/E9OF/IpXJPbyLO9vGzcmXB5FgSP2jfL3LGKJPdV7zUE8rWeKeeI/iueQggOeFa3onrCmhxXZTg==", + "license": "MIT", + "dependencies": { + "oauth": "0.10.x", + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", diff --git a/package.json b/package.json index 5dae615b8..843091424 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "passport-google-oauth20": "2.0.0", "passport-headerapikey": "1.2.2", "passport-jwt": "4.0.1", + "passport-openidconnect": "0.1.2", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", "stripe": "18.5.0", @@ -173,6 +174,7 @@ "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", + "@types/passport-openidconnect": "0.1.3", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", "eslint": "9.35.0", diff --git a/prisma/migrations/20251103162035_added_oidc_to_provider/migration.sql b/prisma/migrations/20251103162035_added_oidc_to_provider/migration.sql new file mode 100644 index 000000000..f71f6eded --- /dev/null +++ b/prisma/migrations/20251103162035_added_oidc_to_provider/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "Provider" ADD VALUE 'OIDC'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 72ec79008..232dde9ca 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -335,6 +335,7 @@ enum Provider { ANONYMOUS GOOGLE INTERNET_IDENTITY + OIDC } enum Role { From e20530d5f041061599f63aa0e24d8bee1dd8980e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 7 Dec 2025 10:04:53 +0100 Subject: [PATCH 075/157] Task/activate 3d hover effect in account membership overview (#6039) * Activate 3d hover effect --- .../user-account-membership/user-account-membership.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.html b/apps/client/src/app/components/user-account-membership/user-account-membership.html index 351d5608a..31d239215 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.html +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -5,6 +5,7 @@ From 5ccfbc31aef808bdd9d41b855963bc4978293c03 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 7 Dec 2025 10:07:47 +0100 Subject: [PATCH 076/157] Release 2.222.0 (#6041) --- 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 7b3f93910..cf976e909 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 +## 2.222.0 - 2025-12-07 ### Added diff --git a/package-lock.json b/package-lock.json index faa39e722..61f38db9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.221.0", + "version": "2.222.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.221.0", + "version": "2.222.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 843091424..d0ee2c084 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.221.0", + "version": "2.222.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 0f7e5a25042ca85b9ade9d993084c1c2139b166f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:16:44 +0100 Subject: [PATCH 077/157] Task/improve OIDC login button label (#6043) * Improve label --- .../login-with-access-token-dialog.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html index cf5611ef7..78604456b 100644 --- a/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html +++ b/apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -52,7 +52,7 @@ class="px-4 rounded-pill" href="../api/auth/oidc" mat-stroked-button - >Sign in with OIDCSign in with OpenID Connect
} From 1cbcb623b505a71afe142b13b208e5c3a4e7db80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:06:37 +0100 Subject: [PATCH 078/157] Feature/update locales (#5993) * Update locales * Update translation * Update changelog --------- Co-authored-by: github-actions[bot] Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 6 ++ apps/client/src/locales/messages.ca.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.de.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.es.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.fr.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.it.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.nl.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.pl.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.pt.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.tr.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.uk.xlf | 78 ++++++++++++++----------- apps/client/src/locales/messages.xlf | 77 +++++++++++++----------- apps/client/src/locales/messages.zh.xlf | 78 ++++++++++++++----------- 13 files changed, 521 insertions(+), 420 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf976e909..f818b9d28 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 + +- Improved the language localization for German (`de`) + ## 2.222.0 - 2025-12-07 ### Added diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index e2740945e..2944e43fb 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -38,11 +38,11 @@
apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -607,7 +607,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457
@@ -643,7 +643,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -795,7 +795,7 @@ Veure Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -803,7 +803,7 @@ Executar Procés apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -811,7 +811,7 @@ Suprimir Procés apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -1503,7 +1503,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -1531,7 +1531,7 @@ Actuar com un altre Usuari apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -1539,7 +1539,7 @@ Eliminar Usuari apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -1623,7 +1623,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -1639,7 +1639,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -1671,7 +1671,7 @@ Oooh! El testimoni de seguretat és incorrecte. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -2043,7 +2043,7 @@ Manteniu la sessió iniciada apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -2471,7 +2471,7 @@ per any apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -2487,7 +2487,7 @@ Prova Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -2495,7 +2495,7 @@ Bescanviar el cupó apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -4076,7 +4076,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -4092,7 +4092,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -5229,7 +5229,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -5301,7 +5301,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -5313,7 +5313,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -5337,7 +5337,7 @@ Clonar libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -5345,7 +5345,7 @@ Exporta l’esborrany com a ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -5929,7 +5929,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -5944,6 +5944,14 @@ 33 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Comprar @@ -6061,7 +6069,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -6209,7 +6217,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -6461,7 +6469,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6729,7 +6737,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7167,7 +7175,7 @@ API Key libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7175,7 +7183,7 @@ Generate Ghostfolio Premium Data Provider API key for self-hosted environments... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Limited Offer! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Get extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 65e71fbd8..02ef33acd 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -254,7 +254,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -290,7 +290,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -418,7 +418,7 @@ Stacktrace anzeigen apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -426,7 +426,7 @@ Job löschen apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -674,7 +674,7 @@ Keine automatische Erneuerung der Mitgliedschaft. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -758,11 +758,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -778,7 +778,7 @@ Ups! Falsches Sicherheits-Token. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -890,7 +890,7 @@ Eingeloggt bleiben apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1226,7 +1226,7 @@ pro Jahr apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -1242,7 +1242,7 @@ Premium ausprobieren apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -1250,7 +1250,7 @@ Gutschein einlösen apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -2254,7 +2254,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -2266,7 +2266,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -2278,7 +2278,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -2286,7 +2286,7 @@ Kopieren libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -2294,7 +2294,7 @@ Geplante Aktivität als ICS exportieren libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -2870,7 +2870,7 @@ Authentifizierung apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -3266,7 +3266,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3278,7 +3278,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -3466,7 +3466,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3726,7 +3726,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -3746,7 +3746,7 @@ Benutzer verwenden apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -3754,7 +3754,7 @@ Benutzer löschen apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -4006,7 +4006,7 @@ Details anzeigen apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4385,6 +4385,14 @@ 73 + + Sign in with OpenID Connect + Einloggen mit OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Kauf @@ -5468,7 +5476,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6093,7 +6101,7 @@ Job ausführen apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6485,7 +6493,7 @@ Position ansehen libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6753,7 +6761,7 @@ Rolle apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7191,7 +7199,7 @@ API-Schlüssel libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7199,7 +7207,7 @@ Ghostfolio Premium Datenanbieter API-Schlüssel für selbst gehostete Umgebungen erstellen... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7555,7 +7563,7 @@ Sicherheits-Token generieren apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Begrenztes Angebot! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Erhalte extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registrierungsdatum apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 2703aebb8..4314903be 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -255,7 +255,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -291,7 +291,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -419,7 +419,7 @@ Visualiza Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -427,7 +427,7 @@ Elimina el trabajo apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -659,7 +659,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -743,11 +743,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -763,7 +763,7 @@ Vaya! Token de seguridad incorrecto. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -875,7 +875,7 @@ Seguir conectado apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1211,7 +1211,7 @@ por año apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -1227,7 +1227,7 @@ Prueba Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -1235,7 +1235,7 @@ Canjea el cupón apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -2239,7 +2239,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -2251,7 +2251,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -2263,7 +2263,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -2271,7 +2271,7 @@ Clonar libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -2279,7 +2279,7 @@ Exportar borrador como ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -2855,7 +2855,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -3251,7 +3251,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3263,7 +3263,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -3451,7 +3451,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3703,7 +3703,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -3723,7 +3723,7 @@ Suplantar usuario apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -3731,7 +3731,7 @@ Eliminar usuario apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -3983,7 +3983,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4362,6 +4362,14 @@ 73 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Comprar @@ -5445,7 +5453,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6070,7 +6078,7 @@ Ejecutar Tarea apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6462,7 +6470,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6730,7 +6738,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7168,7 +7176,7 @@ Clave API libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7176,7 +7184,7 @@ Genere la clave API del proveedor de datos premium de Ghostfolio para entornos autohospedados... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7532,7 +7540,7 @@ Generar token de seguridad apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7908,7 +7916,7 @@ ¡Oferta limitada! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7920,7 +7928,7 @@ Obtén extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8593,7 +8601,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 81da3848d..41dc3d391 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -310,7 +310,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -346,7 +346,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -474,7 +474,7 @@ Voir la Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -482,7 +482,7 @@ Supprimer Tâche apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -870,7 +870,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -966,11 +966,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -986,7 +986,7 @@ Oups! Jeton de Sécurité Incorrect. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1146,7 +1146,7 @@ Rester connecté apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1494,7 +1494,7 @@ par an apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -1510,7 +1510,7 @@ Essayer Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -1518,7 +1518,7 @@ Utiliser un Code Promotionnel apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -2710,7 +2710,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -2722,7 +2722,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -2734,7 +2734,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -2742,7 +2742,7 @@ Dupliquer libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -2750,7 +2750,7 @@ Exporter Brouillon sous ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -3058,7 +3058,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -3250,7 +3250,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3262,7 +3262,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -3450,7 +3450,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3702,7 +3702,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -3722,7 +3722,7 @@ Voir en tant que ... apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -3730,7 +3730,7 @@ Supprimer l’Utilisateur apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -3982,7 +3982,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4361,6 +4361,14 @@ 73 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Achat @@ -5444,7 +5452,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6069,7 +6077,7 @@ Execute la tâche apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6461,7 +6469,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6729,7 +6737,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7167,7 +7175,7 @@ Clé API libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7175,7 +7183,7 @@ Générer la clé API du fournisseur de données Ghostfolio Premium pour les environnements auto-hébergés... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Générer un jeton de sécurité apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Offre Limitée ! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Obtenez supplémentaires apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 6618d2463..e5bc9ccc6 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -255,7 +255,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -291,7 +291,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -419,7 +419,7 @@ Visualizza Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -427,7 +427,7 @@ Elimina il lavoro apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -659,7 +659,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -743,11 +743,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -763,7 +763,7 @@ Ops! Token di sicurezza errato. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -875,7 +875,7 @@ Rimani connesso apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1211,7 +1211,7 @@ per anno apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -1227,7 +1227,7 @@ Prova Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -1235,7 +1235,7 @@ Riscatta il buono apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -2239,7 +2239,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -2251,7 +2251,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -2263,7 +2263,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -2271,7 +2271,7 @@ Clona libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -2279,7 +2279,7 @@ Esporta la bozza come ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -2855,7 +2855,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -3251,7 +3251,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3263,7 +3263,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -3451,7 +3451,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3703,7 +3703,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -3723,7 +3723,7 @@ Imita l’utente apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -3731,7 +3731,7 @@ Elimina l’utente apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -3983,7 +3983,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4362,6 +4362,14 @@ 73 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Compra @@ -5445,7 +5453,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6070,7 +6078,7 @@ Esegui il lavoro apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6462,7 +6470,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6730,7 +6738,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7168,7 +7176,7 @@ API Key libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7176,7 +7184,7 @@ Genera API key per Ghostfolio Premium Data Provider per ambienti self-hosted... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7532,7 +7540,7 @@ Genera Token di Sicurezza apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7908,7 +7916,7 @@ Offerta limitata! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7920,7 +7928,7 @@ Get extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8593,7 +8601,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 43f22714b..5d1bec1b5 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -254,7 +254,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -290,7 +290,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -418,7 +418,7 @@ Bekijk Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -426,7 +426,7 @@ Taak verwijderen apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -658,7 +658,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -742,11 +742,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -762,7 +762,7 @@ Oeps! Onjuiste beveiligingstoken. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -874,7 +874,7 @@ Aangemeld blijven apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1210,7 +1210,7 @@ per jaar apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -1226,7 +1226,7 @@ Probeer Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -1234,7 +1234,7 @@ Coupon inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -2238,7 +2238,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -2250,7 +2250,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -2262,7 +2262,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -2270,7 +2270,7 @@ Kloon libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -2278,7 +2278,7 @@ Concept exporteren als ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -2854,7 +2854,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -3250,7 +3250,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3262,7 +3262,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -3450,7 +3450,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3702,7 +3702,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -3722,7 +3722,7 @@ Gebruiker immiteren apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -3730,7 +3730,7 @@ Gebruiker verwijderen apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -3982,7 +3982,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4361,6 +4361,14 @@ 73 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Koop @@ -5444,7 +5452,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6069,7 +6077,7 @@ Opdracht Uitvoeren apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6461,7 +6469,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6729,7 +6737,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7167,7 +7175,7 @@ API-sleutel libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7175,7 +7183,7 @@ Genereer een Ghostfolio Premium Gegevensleverancier API-sleutel voor zelfgehoste omgevingen... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Beveiligingstoken Aanmaken apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Beperkt aanbod! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Krijg extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 1ae972c8e..6e167a355 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -531,7 +531,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -567,7 +567,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -691,7 +691,7 @@ Wyświetl Stos Wywołań apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -699,7 +699,7 @@ Usuń Zadanie apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -1331,7 +1331,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -1359,7 +1359,7 @@ Wciel się w Użytkownika apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -1367,7 +1367,7 @@ Usuń Użytkownika apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -1459,11 +1459,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -1479,7 +1479,7 @@ Ups! Nieprawidłowy token bezpieczeństwa. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1739,7 +1739,7 @@ Pozostań zalogowany apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -2059,7 +2059,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -2187,7 +2187,7 @@ rocznie apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -2203,7 +2203,7 @@ Wypróbuj Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -2211,7 +2211,7 @@ Wykorzystaj kupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -3703,7 +3703,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -3719,7 +3719,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -4339,7 +4339,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -4756,7 +4756,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -4820,7 +4820,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -4832,7 +4832,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -4848,7 +4848,7 @@ Sklonuj libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -4856,7 +4856,7 @@ Eksportuj Wersję Roboczą jako ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -5296,7 +5296,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -5311,6 +5311,14 @@ 33 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Zakup @@ -5420,7 +5428,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -5568,7 +5576,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -6069,7 +6077,7 @@ Wykonaj Zadanie apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6461,7 +6469,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6729,7 +6737,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7167,7 +7175,7 @@ Klucz API libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7175,7 +7183,7 @@ Generowanie klucza API Ghostfolio Premium Data Provider dla środowisk hostowanych samodzielnie... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Generowanie Tokena Zabezpieczającego apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Oferta ograniczona czasowo! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Uzyskaj dodatkowo apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 9c928731a..8b63acdf6 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -310,7 +310,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -346,7 +346,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -474,7 +474,7 @@ Ver Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -482,7 +482,7 @@ Apagar Tarefa apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -738,7 +738,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -834,11 +834,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -854,7 +854,7 @@ Oops! Token de Segurança Incorreto. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1022,7 +1022,7 @@ Manter sessão iniciada apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1482,7 +1482,7 @@ por ano apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -1498,7 +1498,7 @@ Experimentar Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -1506,7 +1506,7 @@ Resgatar Cupão apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -2610,7 +2610,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -2622,7 +2622,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -2634,7 +2634,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -2642,7 +2642,7 @@ Clonar libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -2650,7 +2650,7 @@ Exportar Rascunho como ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -2902,7 +2902,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -3250,7 +3250,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3262,7 +3262,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -3450,7 +3450,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3702,7 +3702,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -3722,7 +3722,7 @@ Personificar Utilizador apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -3730,7 +3730,7 @@ Apagar Utilizador apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -3982,7 +3982,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4361,6 +4361,14 @@ 73 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Comprar @@ -5444,7 +5452,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6069,7 +6077,7 @@ Executar trabalho apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6461,7 +6469,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6729,7 +6737,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7167,7 +7175,7 @@ Chave de API libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7175,7 +7183,7 @@ Gerar chave de API do Provedor de Dados do Ghostfolio Premium para ambientes auto-hospedados... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Limited Offer! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Get extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 810f91cfa..bc687b3d4 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -491,7 +491,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -527,7 +527,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -655,7 +655,7 @@ Hata İzini Görüntüle apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -663,7 +663,7 @@ İşleri Sil apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -1199,7 +1199,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -1227,7 +1227,7 @@ Kullanıcıyı Taklit Et apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -1235,7 +1235,7 @@ Kullanıcıyı Sil apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -1319,11 +1319,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -1339,7 +1339,7 @@ Hay Allah! Güvenlik anahtarı yanlış. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1599,7 +1599,7 @@ Oturumu açık tut apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1919,7 +1919,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -3183,7 +3183,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -3199,7 +3199,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3827,7 +3827,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -4324,7 +4324,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -4332,7 +4332,7 @@ yıllık apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -4348,7 +4348,7 @@ Premium’u Deneyin apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -4356,7 +4356,7 @@ Kupon Kullan apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -4540,7 +4540,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -4552,7 +4552,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -4568,7 +4568,7 @@ Klonla libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -4576,7 +4576,7 @@ Taslakları ICS Olarak Dışa Aktar libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -4988,7 +4988,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -5003,6 +5003,14 @@ 33 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Al @@ -5096,7 +5104,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -5444,7 +5452,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6069,7 +6077,7 @@ İşlemi Yürüt apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6461,7 +6469,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6729,7 +6737,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7167,7 +7175,7 @@ API Anahtarı libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7175,7 +7183,7 @@ Kendi barındırılan ortamlar için Ghostfolio Premium Veri Sağlayıcı API anahtarı oluştur... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Güvenlik belirteci oluştur apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Sınırlı Teklif! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Get extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 24bc12dd1..166b79a13 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -38,11 +38,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -623,7 +623,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -659,7 +659,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -819,7 +819,7 @@ Переглянути трасування apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -827,7 +827,7 @@ Виконати завдання apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -835,7 +835,7 @@ Видалити завдання apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -1427,7 +1427,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -1567,7 +1567,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -1647,7 +1647,7 @@ Видавати себе за користувача apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -1655,7 +1655,7 @@ Видалити користувача apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -1739,7 +1739,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -1755,7 +1755,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -1779,7 +1779,7 @@ Упс! Неправильний Секретний Токен. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -2135,7 +2135,7 @@ Залишатися в системі apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -2759,7 +2759,7 @@ на рік apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -2775,7 +2775,7 @@ Спробуйте Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -2783,7 +2783,7 @@ Обміняти купон apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -4376,7 +4376,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -4392,7 +4392,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -5947,7 +5947,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -6019,7 +6019,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -6031,7 +6031,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -6055,7 +6055,7 @@ Клонувати libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -6063,7 +6063,7 @@ Експортувати чернетку як ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -6763,7 +6763,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -6783,7 +6783,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -6794,6 +6794,14 @@ 34 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy Купити @@ -6911,7 +6919,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -7063,7 +7071,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -7223,7 +7231,7 @@ Ключ API libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7231,7 +7239,7 @@ Згенерувати ключ API для постачальника даних Ghostfolio Premium для self-hosted середовищ... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7531,7 +7539,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7907,7 +7915,7 @@ Limited Offer! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7919,7 +7927,7 @@ Get extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8592,7 +8600,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 800f238ef..92d1c9639 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -509,7 +509,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -544,7 +544,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -656,14 +656,14 @@ View Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 Delete Job apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -1254,7 +1254,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -1279,14 +1279,14 @@ Impersonate User apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 Delete User apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -1370,11 +1370,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -1389,7 +1389,7 @@ Oops! Incorrect Security Token. apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1627,7 +1627,7 @@ Stay signed in apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -1919,7 +1919,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -2034,7 +2034,7 @@ per year apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -2049,14 +2049,14 @@ Try Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 Redeem Coupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -3417,7 +3417,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -3432,7 +3432,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -3989,7 +3989,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -4378,7 +4378,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -4442,7 +4442,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -4453,7 +4453,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -4467,14 +4467,14 @@ Clone libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 Export Draft as ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -4888,7 +4888,7 @@ View Details apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -4902,6 +4902,13 @@ 33 + + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy @@ -5000,7 +5007,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -5130,7 +5137,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -5541,7 +5548,7 @@ Execute Job apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -5841,7 +5848,7 @@ View Holding libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6058,7 +6065,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -6488,14 +6495,14 @@ Generate Ghostfolio Premium Data Provider API key for self-hosted environments... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 API Key libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -6854,7 +6861,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7168,7 +7175,7 @@ Limited Offer! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7179,7 +7186,7 @@ Get extra apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -7772,7 +7779,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 5c2508f8d..6a9abc040 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -540,7 +540,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 453 + 457 @@ -576,7 +576,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 480 + 484 libs/ui/src/lib/benchmark/benchmark.component.html @@ -700,7 +700,7 @@ 查看堆栈跟踪 apps/client/src/app/components/admin-jobs/admin-jobs.html - 215 + 216 @@ -708,7 +708,7 @@ 删除任务 apps/client/src/app/components/admin-jobs/admin-jobs.html - 222 + 224 @@ -1340,7 +1340,7 @@ No auto-renewal on membership. apps/client/src/app/components/user-account-membership/user-account-membership.html - 73 + 74 @@ -1368,7 +1368,7 @@ 模拟用户 apps/client/src/app/components/admin-users/admin-users.html - 232 + 234 @@ -1376,7 +1376,7 @@ 删除用户 apps/client/src/app/components/admin-users/admin-users.html - 253 + 255 @@ -1468,11 +1468,11 @@ apps/client/src/app/components/header/header.component.ts - 290 + 297 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 79 libs/common/src/lib/routes/routes.ts @@ -1488,7 +1488,7 @@ 哎呀!安全令牌不正确。 apps/client/src/app/components/header/header.component.ts - 305 + 312 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1748,7 +1748,7 @@ 保持登录 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 55 + 66 @@ -2068,7 +2068,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 20 + 21 apps/client/src/app/pages/pricing/pricing-page.html @@ -2196,7 +2196,7 @@ 每年 apps/client/src/app/components/user-account-membership/user-account-membership.html - 32 + 33 apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -2212,7 +2212,7 @@ 尝试高级版 apps/client/src/app/components/user-account-membership/user-account-membership.html - 52 + 53 @@ -2220,7 +2220,7 @@ 兑换优惠券 apps/client/src/app/components/user-account-membership/user-account-membership.html - 66 + 67 @@ -3712,7 +3712,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 382 + 383 @@ -3728,7 +3728,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 394 + 397 @@ -4348,7 +4348,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.html - 18 + 19 apps/client/src/app/pages/pricing/pricing-page.html @@ -4777,7 +4777,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 37 + 40 @@ -4849,7 +4849,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 407 + 411 @@ -4861,7 +4861,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 420 + 424 @@ -4877,7 +4877,7 @@ 克隆 libs/ui/src/lib/activities-table/activities-table.component.html - 459 + 463 @@ -4885,7 +4885,7 @@ 将汇票导出为 ICS libs/ui/src/lib/activities-table/activities-table.component.html - 469 + 473 @@ -5341,7 +5341,7 @@ 查看详细信息 apps/client/src/app/components/admin-users/admin-users.html - 225 + 226 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -5356,6 +5356,14 @@ 33 + + Sign in with OpenID Connect + Sign in with OpenID Connect + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + Buy 买入 @@ -5465,7 +5473,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 32 + 35 @@ -5613,7 +5621,7 @@ libs/ui/src/lib/membership-card/membership-card.component.html - 42 + 45 @@ -6070,7 +6078,7 @@ 执行作业 apps/client/src/app/components/admin-jobs/admin-jobs.html - 218 + 220 @@ -6462,7 +6470,7 @@ 查看持仓 libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 450 @@ -6730,7 +6738,7 @@ 角色 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 36 + 14 @@ -7168,7 +7176,7 @@ API 密钥 libs/ui/src/lib/membership-card/membership-card.component.html - 18 + 21 @@ -7176,7 +7184,7 @@ 为自托管环境生成 Ghostfolio Premium 数据提供者 API 密钥... libs/ui/src/lib/membership-card/membership-card.component.html - 26 + 29 @@ -7532,7 +7540,7 @@ 生成安全令牌 apps/client/src/app/components/admin-users/admin-users.html - 242 + 244 @@ -7908,7 +7916,7 @@ 限时优惠! apps/client/src/app/components/user-account-membership/user-account-membership.html - 40 + 41 apps/client/src/app/pages/pricing/pricing-page.html @@ -7920,7 +7928,7 @@ 获取额外 apps/client/src/app/components/user-account-membership/user-account-membership.html - 43 + 44 apps/client/src/app/pages/pricing/pricing-page.html @@ -8593,7 +8601,7 @@ 注册日期 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 20 + 26 From a36f87d59268ae71951a4c6b7c5c9c53536b9e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Mon, 8 Dec 2025 19:43:05 +0100 Subject: [PATCH 079/157] Task/add OpenID Connect (OIDC) configuration details to README (#6044) * Add OpenID Connect (OIDC) configuration details --- README.md | 51 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1a5cc6e95..822825b57 100644 --- a/README.md +++ b/README.md @@ -85,24 +85,39 @@ 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, e.g. `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer` | -| `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 | +| 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, e.g. `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer` | +| `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. | + +#### OpenID Connect OIDC (Experimental) + +| Name | Type | Default Value | Description | +| -------------------------- | --------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------- | +| `ENABLE_FEATURE_AUTH_OIDC` | `boolean` (optional) | `false` | Enables _OpenID Connect_ authentication | +| `OIDC_AUTHORIZATION_URL` | `string` (optional) | | Manual override for the OIDC authorization endpoint (falls back to the discovery from the issuer) | +| `OIDC_CALLBACK_URL` | `string` (optional) | `${ROOT_URL}/api/auth/oidc/callback` | The OIDC callback URL | +| `OIDC_CLIENT_ID` | `string` | | The OIDC client ID | +| `OIDC_CLIENT_SECRET` | `string` | | The OIDC client secret | +| `OIDC_ISSUER` | `string` | | The OIDC issuer URL, used to discover the OIDC configuration via `/.well-known/openid-configuration` | +| `OIDC_SCOPE` | `string[]` (optional) | `["openid"]` | The OIDC scope to request, e.g. `["email","openid","profile"]` | +| `OIDC_TOKEN_URL` | `string` (optional) | | Manual override for the OIDC token endpoint (falls back to the discovery from the issuer) | +| `OIDC_USER_INFO_URL` | `string` (optional) | | Manual override for the OIDC user info endpoint (falls back to the discovery from the issuer) | ### Run with Docker Compose From 76e06ed59f9cd266088c496a806d4f5fcd4fe85a Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Tue, 9 Dec 2025 02:01:22 +0700 Subject: [PATCH 080/157] Task/move notification service to UI library (#6048) * Move notification service to UI library * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/app.component.ts | 2 +- .../src/app/components/access-table/access-table.component.ts | 2 +- .../src/app/components/admin-jobs/admin-jobs.component.ts | 2 +- .../components/admin-market-data/admin-market-data.service.ts | 2 +- .../asset-profile-dialog/asset-profile-dialog.component.ts | 2 +- .../app/components/admin-overview/admin-overview.component.ts | 2 +- .../app/components/admin-platform/admin-platform.component.ts | 2 +- .../app/components/admin-settings/admin-settings.component.ts | 2 +- .../src/app/components/admin-tag/admin-tag.component.ts | 2 +- .../src/app/components/admin-users/admin-users.component.ts | 2 +- apps/client/src/app/components/header/header.component.ts | 2 +- .../portfolio-performance/portfolio-performance.component.ts | 2 +- .../portfolio-summary/portfolio-summary.component.ts | 2 +- .../create-or-update-access-dialog.component.ts | 2 +- .../user-account-access/user-account-access.component.ts | 2 +- .../user-account-membership.component.ts | 2 +- .../user-account-settings/user-account-settings.component.ts | 2 +- apps/client/src/app/core/layout.service.ts | 4 ++-- apps/client/src/app/pages/accounts/accounts-page.component.ts | 2 +- apps/client/src/app/pages/demo/demo-page.component.ts | 2 +- apps/client/src/app/pages/pricing/pricing-page.component.ts | 2 +- apps/client/src/main.ts | 2 +- .../ui/src/lib/account-balances/account-balances.component.ts | 3 +-- .../lib/accounts-table/accounts-table.component.stories.ts | 2 +- libs/ui/src/lib/accounts-table/accounts-table.component.ts | 3 +-- .../activities-table/activities-table.component.stories.ts | 2 +- .../ui/src/lib/activities-table/activities-table.component.ts | 3 +-- libs/ui/src/lib/benchmark/benchmark.component.ts | 3 +-- .../lib/notifications}/alert-dialog/alert-dialog.component.ts | 0 .../ui/src/lib/notifications}/alert-dialog/alert-dialog.html | 0 .../ui/src/lib/notifications}/alert-dialog/alert-dialog.scss | 0 .../lib/notifications}/alert-dialog/interfaces/interfaces.ts | 0 .../confirmation-dialog/confirmation-dialog.component.ts | 0 .../confirmation-dialog/confirmation-dialog.html | 0 .../confirmation-dialog/confirmation-dialog.scss | 0 .../confirmation-dialog/interfaces/interfaces.ts | 0 libs/ui/src/lib/notifications/index.ts | 3 +++ .../ui/src/lib/notifications}/interfaces/interfaces.ts | 0 .../ui/src/lib/notifications}/notification.module.ts | 0 .../ui/src/lib/notifications}/notification.service.ts | 0 .../notifications}/prompt-dialog/prompt-dialog.component.ts | 0 .../src/lib/notifications}/prompt-dialog/prompt-dialog.html | 0 43 files changed, 33 insertions(+), 33 deletions(-) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/alert-dialog/alert-dialog.component.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/alert-dialog/alert-dialog.html (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/alert-dialog/alert-dialog.scss (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/alert-dialog/interfaces/interfaces.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/confirmation-dialog/confirmation-dialog.component.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/confirmation-dialog/confirmation-dialog.html (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/confirmation-dialog/confirmation-dialog.scss (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/confirmation-dialog/interfaces/interfaces.ts (100%) create mode 100644 libs/ui/src/lib/notifications/index.ts rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/interfaces/interfaces.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/notification.module.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/notification.service.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/prompt-dialog/prompt-dialog.component.ts (100%) rename {apps/client/src/app/core/notification => libs/ui/src/lib/notifications}/prompt-dialog/prompt-dialog.html (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f818b9d28..7ceb94a11 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 +- Moved the notification module to `@ghostfolio/ui` - Improved the language localization for German (`de`) ## 2.222.0 - 2025-12-07 diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index de82c7d9c..12a7b0de9 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -3,6 +3,7 @@ import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; import { ColorScheme } from '@ghostfolio/common/types'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { ChangeDetectionStrategy, @@ -35,7 +36,6 @@ import { GfFooterComponent } from './components/footer/footer.component'; import { GfHeaderComponent } from './components/header/header.component'; import { GfHoldingDetailDialogComponent } from './components/holding-detail-dialog/holding-detail-dialog.component'; import { HoldingDetailDialogParams } from './components/holding-detail-dialog/interfaces/interfaces'; -import { NotificationService } from './core/notification/notification.service'; import { DataService } from './services/data.service'; import { ImpersonationStorageService } from './services/impersonation-storage.service'; import { TokenStorageService } from './services/token-storage.service'; diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts index 1127e5629..fe2c81199 100644 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ b/apps/client/src/app/components/access-table/access-table.component.ts @@ -1,7 +1,7 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { Access, User } from '@ghostfolio/common/interfaces'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index 8ed72445f..66bac76f5 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -1,4 +1,3 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -9,6 +8,7 @@ import { } from '@ghostfolio/common/config'; import { getDateWithTimeFormatString } from '@ghostfolio/common/helper'; import { AdminJobs, User } from '@ghostfolio/common/interfaces'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index 3f60cb8c5..eaad32c0e 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -1,4 +1,3 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -11,6 +10,7 @@ import { AssetProfileIdentifier, AdminMarketDataItem } from '@ghostfolio/common/interfaces'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { Injectable } from '@angular/core'; import { EMPTY, catchError, finalize, forkJoin } from 'rxjs'; diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index b2ef4f17b..3fe944a25 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -1,5 +1,4 @@ import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -24,6 +23,7 @@ import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor'; import { translate } from '@ghostfolio/ui/i18n'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 0b4b36d06..e4be7b062 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -1,4 +1,3 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { CacheService } from '@ghostfolio/client/services/cache.service'; import { DataService } from '@ghostfolio/client/services/data.service'; @@ -20,6 +19,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 64e7ff7cf..832a70503 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,10 +1,10 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index ec44b6e65..cabf4e589 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -1,7 +1,6 @@ import { GfAdminPlatformComponent } from '@ghostfolio/client/components/admin-platform/admin-platform.component'; import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -15,6 +14,7 @@ import { } from '@ghostfolio/common/interfaces'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index a891baa45..305eb4628 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,8 +1,8 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 6c366a16c..99fbe7901 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,6 +1,5 @@ import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; @@ -19,6 +18,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 80239d56f..b7bf4cb98 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -1,7 +1,6 @@ import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces'; import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component'; import { LayoutService } from '@ghostfolio/client/core/layout.service'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { @@ -17,6 +16,7 @@ import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; import { DateRange } from '@ghostfolio/common/types'; import { GfAssistantComponent } from '@ghostfolio/ui/assistant/assistant.component'; import { GfLogoComponent } from '@ghostfolio/ui/logo'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts index c98a26831..04c0f507c 100644 --- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts +++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts @@ -1,4 +1,3 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { getLocale, getNumberFormatDecimal, @@ -8,6 +7,7 @@ import { PortfolioPerformance, ResponseError } from '@ghostfolio/common/interfaces'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; 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 7eab9172e..9e9bb13d3 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 @@ -1,8 +1,8 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; import { getDateFnsLocale, getLocale } from '@ghostfolio/common/helper'; import { PortfolioSummary, User } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 9aa07feee..05c047dc6 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -1,7 +1,7 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/user-account-access/user-account-access.component.ts b/apps/client/src/app/components/user-account-access/user-account-access.component.ts index da2e8f508..11960b8aa 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,5 +1,4 @@ import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -7,6 +6,7 @@ import { CreateAccessDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { 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 ae9183c13..069ea4b0e 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 @@ -1,4 +1,3 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -7,6 +6,7 @@ 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'; import { CommonModule } from '@angular/common'; 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 32e3d132e..e17425676 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 @@ -1,4 +1,3 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { KEY_STAY_SIGNED_IN, @@ -12,6 +11,7 @@ import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { downloadAsFile } from '@ghostfolio/common/helper'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/core/layout.service.ts b/apps/client/src/app/core/layout.service.ts index a6fb65006..fd435acdb 100644 --- a/apps/client/src/app/core/layout.service.ts +++ b/apps/client/src/app/core/layout.service.ts @@ -1,9 +1,9 @@ +import { NotificationService } from '@ghostfolio/ui/notifications'; + import { Injectable } from '@angular/core'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Observable, Subject } from 'rxjs'; -import { NotificationService } from './notification/notification.service'; - @Injectable({ providedIn: 'root' }) diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 2bb6457a5..2b496e4fb 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -1,6 +1,5 @@ import { GfAccountDetailDialogComponent } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component'; import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces'; -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -12,6 +11,7 @@ import { import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; diff --git a/apps/client/src/app/pages/demo/demo-page.component.ts b/apps/client/src/app/pages/demo/demo-page.component.ts index 720bb4974..9eba64788 100644 --- a/apps/client/src/app/pages/demo/demo-page.component.ts +++ b/apps/client/src/app/pages/demo/demo-page.component.ts @@ -1,7 +1,7 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { InfoItem } from '@ghostfolio/common/interfaces'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { Component, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 88958ea0d..6ed8dfd31 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -1,10 +1,10 @@ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { translate } from '@ghostfolio/ui/i18n'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index fc8a9ef7a..2a22b7b7b 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -1,6 +1,7 @@ import { locale } from '@ghostfolio/common/config'; import { InfoResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; +import { GfNotificationModule } from '@ghostfolio/ui/notifications'; import { Platform } from '@angular/cdk/platform'; import { @@ -33,7 +34,6 @@ import { authInterceptorProviders } from './app/core/auth.interceptor'; import { httpResponseInterceptorProviders } from './app/core/http-response.interceptor'; import { LanguageService } from './app/core/language.service'; import { ModulePreloadService } from './app/core/module-preload.service'; -import { GfNotificationModule } from './app/core/notification/notification.module'; import { PageTitleStrategy } from './app/services/page-title.strategy'; import { environment } from './environments/environment'; diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index 5fe47347e..608ee1c75 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -1,10 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { DATE_FORMAT, getLocale } from '@ghostfolio/common/helper'; import { AccountBalancesResponse } from '@ghostfolio/common/interfaces'; import { validateObjectForForm } from '@ghostfolio/common/utils'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { CUSTOM_ELEMENTS_SCHEMA, diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts index aeda82fd9..53c59a95f 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts @@ -7,10 +7,10 @@ import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; -import { NotificationService } from 'apps/client/src/app/core/notification/notification.service'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { GfEntityLogoComponent } from '../entity-logo'; +import { NotificationService } from '../notifications'; import { GfValueComponent } from '../value'; import { GfAccountsTableComponent } from './accounts-table.component'; diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index 898231168..699de6d7e 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -1,8 +1,7 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale } from '@ghostfolio/common/helper'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts index a0ad690d7..e7a2ba819 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts @@ -13,12 +13,12 @@ import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; -import { NotificationService } from 'apps/client/src/app/core/notification/notification.service'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { GfActivityTypeComponent } from '../activity-type/activity-type.component'; import { GfEntityLogoComponent } from '../entity-logo'; import { GfNoTransactionsInfoComponent } from '../no-transactions-info/no-transactions-info.component'; +import { NotificationService } from '../notifications'; import { GfValueComponent } from '../value'; import { GfActivitiesTableComponent } from './activities-table.component'; diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index 476fca5fb..e53f37872 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { DEFAULT_PAGE_SIZE, TAG_ID_EXCLUDE_FROM_ANALYSIS @@ -12,6 +10,7 @@ import { } from '@ghostfolio/common/interfaces'; import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { OrderWithAccount } from '@ghostfolio/common/types'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { SelectionModel } from '@angular/cdk/collections'; import { CommonModule } from '@angular/common'; diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index 5793300c1..fe53240ed 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper'; import { @@ -7,6 +5,7 @@ import { Benchmark, User } from '@ghostfolio/common/interfaces'; +import { NotificationService } from '@ghostfolio/ui/notifications'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts similarity index 100% rename from apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts rename to libs/ui/src/lib/notifications/alert-dialog/alert-dialog.component.ts diff --git a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.html b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html similarity index 100% rename from apps/client/src/app/core/notification/alert-dialog/alert-dialog.html rename to libs/ui/src/lib/notifications/alert-dialog/alert-dialog.html diff --git a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.scss b/libs/ui/src/lib/notifications/alert-dialog/alert-dialog.scss similarity index 100% rename from apps/client/src/app/core/notification/alert-dialog/alert-dialog.scss rename to libs/ui/src/lib/notifications/alert-dialog/alert-dialog.scss diff --git a/apps/client/src/app/core/notification/alert-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/alert-dialog/interfaces/interfaces.ts similarity index 100% rename from apps/client/src/app/core/notification/alert-dialog/interfaces/interfaces.ts rename to libs/ui/src/lib/notifications/alert-dialog/interfaces/interfaces.ts diff --git a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts b/libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.component.ts similarity index 100% rename from apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts rename to libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.component.ts diff --git a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.html b/libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.html similarity index 100% rename from apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.html rename to libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.html diff --git a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.scss b/libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.scss similarity index 100% rename from apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.scss rename to libs/ui/src/lib/notifications/confirmation-dialog/confirmation-dialog.scss diff --git a/apps/client/src/app/core/notification/confirmation-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/confirmation-dialog/interfaces/interfaces.ts similarity index 100% rename from apps/client/src/app/core/notification/confirmation-dialog/interfaces/interfaces.ts rename to libs/ui/src/lib/notifications/confirmation-dialog/interfaces/interfaces.ts diff --git a/libs/ui/src/lib/notifications/index.ts b/libs/ui/src/lib/notifications/index.ts new file mode 100644 index 000000000..864083b16 --- /dev/null +++ b/libs/ui/src/lib/notifications/index.ts @@ -0,0 +1,3 @@ +export * from './interfaces/interfaces'; +export * from './notification.module'; +export * from './notification.service'; diff --git a/apps/client/src/app/core/notification/interfaces/interfaces.ts b/libs/ui/src/lib/notifications/interfaces/interfaces.ts similarity index 100% rename from apps/client/src/app/core/notification/interfaces/interfaces.ts rename to libs/ui/src/lib/notifications/interfaces/interfaces.ts diff --git a/apps/client/src/app/core/notification/notification.module.ts b/libs/ui/src/lib/notifications/notification.module.ts similarity index 100% rename from apps/client/src/app/core/notification/notification.module.ts rename to libs/ui/src/lib/notifications/notification.module.ts diff --git a/apps/client/src/app/core/notification/notification.service.ts b/libs/ui/src/lib/notifications/notification.service.ts similarity index 100% rename from apps/client/src/app/core/notification/notification.service.ts rename to libs/ui/src/lib/notifications/notification.service.ts diff --git a/apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.component.ts b/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts similarity index 100% rename from apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.component.ts rename to libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.component.ts diff --git a/apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.html b/libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.html similarity index 100% rename from apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.html rename to libs/ui/src/lib/notifications/prompt-dialog/prompt-dialog.html From 0683c5b69b2512043514bb2d2b50963ed0a2d68a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:24:22 +0100 Subject: [PATCH 081/157] Task/update OSS Friends (#6047) * Update OSS Friends --- apps/client/src/assets/oss-friends.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/assets/oss-friends.json b/apps/client/src/assets/oss-friends.json index 8a64650fe..2d5b994d3 100644 --- a/apps/client/src/assets/oss-friends.json +++ b/apps/client/src/assets/oss-friends.json @@ -1,5 +1,5 @@ { - "createdAt": "2025-11-21T00:00:00.000Z", + "createdAt": "2025-12-08T00:00:00.000Z", "data": [ { "name": "Activepieces", @@ -76,6 +76,11 @@ "description": "Mockoon is the easiest and quickest way to design and run mock REST APIs.", "href": "https://mockoon.com" }, + { + "name": "Onyx", + "description": "Onyx is the open-source AI chat connected to your docs, apps, and people.", + "href": "https://onyx.app" + }, { "name": "OpenBB", "description": "Democratizing investment research through an open source financial ecosystem. The OpenBB Terminal allows everyone to perform investment research, from everywhere.", From 2940e615858da85486a208c3023fc12e83d407ef Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:05:02 +0100 Subject: [PATCH 082/157] Task/update VS Code extension of Prettier (#6010) * Update VS Code extension of Prettier --- .vscode/extensions.json | 4 ++-- .vscode/settings.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 68abade5f..fde9874a2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ { "recommendations": [ "angular.ng-template", - "esbenp.prettier-vscode", "firsttris.vscode-jest-runner", - "nrwl.angular-console" + "nrwl.angular-console", + "prettier.prettier-vscode" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bf4d12b5..36091af85 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.defaultFormatter": "prettier.prettier-vscode", "editor.formatOnSave": true } From 646ee7271a27dce5309d3829ed654baefcaf7518 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:22:24 -0600 Subject: [PATCH 083/157] Bugfix/allocate remaining percentage to unknown data in portfolio proportion chart (#6054) * Allocate remaining percentage to unknown data in portfolio proportion chart * Update changelog --- CHANGELOG.md | 4 +++ .../portfolio-proportion-chart.component.ts | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ceb94a11..f86d7c01a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved the notification module to `@ghostfolio/ui` - Improved the language localization for German (`de`) +### Fixed + +- Fixed a calculation issue that resulted in the incorrect assignment of unknown data in the portfolio proportion chart component + ## 2.222.0 - 2025-12-07 ### Added diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 2d8a03ac0..fb11897eb 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -1,6 +1,6 @@ import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; -import { getLocale, getTextColor } from '@ghostfolio/common/helper'; +import { getLocale, getSum, getTextColor } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, PortfolioPosition @@ -193,6 +193,30 @@ export class GfPortfolioProportionChartComponent }); } + if (this.isInPercent) { + const totalValueInPercentage = getSum( + Object.values(chartData).map(({ value }) => { + return value; + }) + ); + + const unknownValueInPercentage = new Big(1).minus(totalValueInPercentage); + + if (unknownValueInPercentage.gt(0)) { + // If total is below 100%, allocate the remaining percentage to UNKNOWN_KEY + if (chartData[UNKNOWN_KEY]) { + chartData[UNKNOWN_KEY].value = chartData[UNKNOWN_KEY].value.plus( + unknownValueInPercentage + ); + } else { + chartData[UNKNOWN_KEY] = { + name: UNKNOWN_KEY, + value: unknownValueInPercentage + }; + } + } + } + let chartDataSorted = Object.entries(chartData) .sort((a, b) => { return a[1].value.minus(b[1].value).toNumber(); From de3f0c42079a3b475c463e5d320d7bd70de0e395 Mon Sep 17 00:00:00 2001 From: Johnson Towoju Date: Sat, 13 Dec 2025 19:52:18 +0100 Subject: [PATCH 084/157] Feature/extend FIRE page with projection information at retirement date (#6034) * Extend FIRE page with projection information at retirement date * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 4 + apps/api/src/app/user/user.service.ts | 15 ++ .../portfolio/fire/fire-page.component.ts | 40 +++- .../app/pages/portfolio/fire/fire-page.html | 178 ++++++++++++------ ...re-calculation-complete-event.interface.ts | 4 + libs/common/src/lib/interfaces/index.ts | 2 + .../fire-calculator.component.ts | 21 ++- 7 files changed, 197 insertions(+), 67 deletions(-) create mode 100644 libs/common/src/lib/interfaces/fire-calculation-complete-event.interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f86d7c01a..dab53a614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Included wealth projection data calculated for the retirement date in the _FIRE_ section (experimental) + ### Changed - Moved the notification module to `@ghostfolio/ui` diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 65ce92cb2..3280fbfac 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -248,6 +248,11 @@ export class UserService { }; } + // Set default value for annual interest rate + if (!(user.settings.settings as UserSettings)?.annualInterestRate) { + (user.settings.settings as UserSettings).annualInterestRate = 5; + } + // Set default value for base currency if (!(user.settings.settings as UserSettings)?.baseCurrency) { (user.settings.settings as UserSettings).baseCurrency = DEFAULT_CURRENCY; @@ -265,11 +270,21 @@ export class UserService { PerformanceCalculationType.ROAI; } + // Set default value for projected total amount + if (!(user.settings.settings as UserSettings)?.projectedTotalAmount) { + (user.settings.settings as UserSettings).projectedTotalAmount = 0; + } + // Set default value for safe withdrawal rate if (!(user.settings.settings as UserSettings)?.safeWithdrawalRate) { (user.settings.settings as UserSettings).safeWithdrawalRate = 0.04; } + // Set default value for savings rate + if (!(user.settings.settings as UserSettings)?.savingsRate) { + (user.settings.settings as UserSettings).savingsRate = 0; + } + // Set default value for view mode if (!(user.settings.settings as UserSettings).viewMode) { (user.settings.settings as UserSettings).viewMode = 'DEFAULT'; diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 63187c05c..da1378d22 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -1,7 +1,11 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { FireWealth, User } from '@ghostfolio/common/interfaces'; +import { + FireCalculationCompleteEvent, + FireWealth, + User +} from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; @@ -38,11 +42,15 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public hasImpersonationId: boolean; public hasPermissionToUpdateUserSettings: boolean; public isLoading = false; + public projectedTotalAmount: number; + public retirementDate: Date; public safeWithdrawalRateControl = new FormControl(undefined); public safeWithdrawalRateOptions = [0.025, 0.03, 0.035, 0.04, 0.045]; public user: User; public withdrawalRatePerMonth: Big; + public withdrawalRatePerMonthProjected: Big; public withdrawalRatePerYear: Big; + public withdrawalRatePerYearProjected: Big; private unsubscribeSubject = new Subject(); @@ -79,8 +87,6 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.calculateWithdrawalRates(); - this.isLoading = false; - this.changeDetectorRef.markForCheck(); }); @@ -139,6 +145,18 @@ export class GfFirePageComponent implements OnDestroy, OnInit { }); } + public onCalculationComplete({ + projectedTotalAmount, + retirementDate + }: FireCalculationCompleteEvent) { + this.projectedTotalAmount = projectedTotalAmount; + this.retirementDate = retirementDate; + + this.calculateWithdrawalRatesProjected(); + + this.isLoading = false; + } + public onRetirementDateChange(retirementDate: Date) { this.dataService .putUserSetting({ @@ -170,6 +188,7 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.user = user; this.calculateWithdrawalRates(); + this.calculateWithdrawalRatesProjected(); this.changeDetectorRef.markForCheck(); }); @@ -225,4 +244,19 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12); } } + + private calculateWithdrawalRatesProjected() { + if ( + this.fireWealth && + this.projectedTotalAmount && + this.user?.settings?.safeWithdrawalRate + ) { + this.withdrawalRatePerYearProjected = new Big( + this.projectedTotalAmount + ).mul(this.user.settings.safeWithdrawalRate); + + this.withdrawalRatePerMonthProjected = + this.withdrawalRatePerYearProjected.div(12); + } + } } diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index ce51717fa..a4a17fa86 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -28,6 +28,7 @@ [retirementDate]="user?.settings?.retirementDate" [savingsRate]="user?.settings?.savingsRate" (annualInterestRateChanged)="onAnnualInterestRateChange($event)" + (calculationCompleted)="onCalculationComplete($event)" (projectedTotalAmountChanged)="onProjectedTotalAmountChange($event)" (retirementDateChanged)="onRetirementDateChange($event)" (savingsRateChanged)="onSavingsRateChange($event)" @@ -62,74 +63,129 @@ } @else {
- If you retire today, you would be able to withdraw -   - -   - per year -   - or -   - +
+ If you retire today, you would be able to withdraw   - per month, -   - based on your total assets of -   - - -   - and a safe withdrawal rate (SWR) of - @if ( - !hasImpersonationId && - hasPermissionToUpdateUserSettings && - user?.settings?.isExperimentalFeatures - ) { - . - } @else { +   + or   . + [unit]="user?.settings?.baseCurrency" + [value]="withdrawalRatePerMonth?.toNumber()" + /> +   + per month, +   + based on your total assets of +   + + +   + and a safe withdrawal rate (SWR) of + @if ( + !hasImpersonationId && + hasPermissionToUpdateUserSettings && + user?.settings?.isExperimentalFeatures + ) { + . + } @else { +   + . + } +
+ + @if (user?.settings?.isExperimentalFeatures) { +
+ By +   + {{ + user?.settings?.retirementDate ?? retirementDate + | date: 'MMMM yyyy' + }} + , +   + this is projected to increase to +   + +   + per year +   + or +   + +   + per month, +   + assuming a +   + +   + annual interest rate. +
}
} diff --git a/libs/common/src/lib/interfaces/fire-calculation-complete-event.interface.ts b/libs/common/src/lib/interfaces/fire-calculation-complete-event.interface.ts new file mode 100644 index 000000000..1238b0729 --- /dev/null +++ b/libs/common/src/lib/interfaces/fire-calculation-complete-event.interface.ts @@ -0,0 +1,4 @@ +export interface FireCalculationCompleteEvent { + projectedTotalAmount: number; + retirementDate: Date; +} diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 1d7991e40..4b8e8009a 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -18,6 +18,7 @@ import type { DataProviderInfo } from './data-provider-info.interface'; import type { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; import type { FilterGroup } from './filter-group.interface'; import type { Filter } from './filter.interface'; +import type { FireCalculationCompleteEvent } from './fire-calculation-complete-event.interface'; import type { FireWealth } from './fire-wealth.interface'; import type { HistoricalDataItem } from './historical-data-item.interface'; import type { HoldingWithParents } from './holding-with-parents.interface'; @@ -140,6 +141,7 @@ export { ExportResponse, Filter, FilterGroup, + FireCalculationCompleteEvent, FireWealth, HistoricalDataItem, HistoricalResponse, diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts index 44276ec43..655798b3d 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -4,6 +4,7 @@ import { } from '@ghostfolio/common/chart-helper'; import { primaryColorRgb } from '@ghostfolio/common/config'; import { getLocale } from '@ghostfolio/common/helper'; +import { FireCalculationCompleteEvent } from '@ghostfolio/common/interfaces'; import { ColorScheme } from '@ghostfolio/common/types'; import { CommonModule } from '@angular/common'; @@ -88,6 +89,8 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { @Input() savingsRate: number; @Output() annualInterestRateChanged = new EventEmitter(); + @Output() calculationCompleted = + new EventEmitter(); @Output() projectedTotalAmountChanged = new EventEmitter(); @Output() retirementDateChanged = new EventEmitter(); @Output() savingsRateChanged = new EventEmitter(); @@ -131,6 +134,18 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.initialize(); }); + this.calculatorForm.valueChanges + .pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + const { projectedTotalAmount, retirementDate } = + this.calculatorForm.getRawValue(); + + this.calculationCompleted.emit({ + projectedTotalAmount, + retirementDate + }); + }); + this.calculatorForm .get('annualInterestRate') .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) @@ -161,10 +176,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { if (isNumber(this.fireWealth) && this.fireWealth >= 0) { this.calculatorForm.setValue( { - annualInterestRate: this.annualInterestRate ?? 5, - paymentPerPeriod: this.savingsRate ?? 0, + annualInterestRate: this.annualInterestRate, + paymentPerPeriod: this.savingsRate, principalInvestmentAmount: this.fireWealth, - projectedTotalAmount: this.projectedTotalAmount ?? 0, + projectedTotalAmount: this.projectedTotalAmount, retirementDate: this.retirementDate ?? this.DEFAULT_RETIREMENT_DATE }, { From ec3e8520eba1c0687db98b2b394e6c498ad53186 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 13 Dec 2025 19:56:04 +0100 Subject: [PATCH 085/157] Task/update note in personal finance tools (#6053) * Update note --- libs/common/src/lib/personal-finance-tools.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 68d6dfa22..7d1c4434a 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -1021,9 +1021,11 @@ export const personalFinanceTools: Product[] = [ }, { hasSelfHostingAbility: false, + isArchived: true, key: 'wallmine', languages: ['English'], name: 'wallmine', + note: 'wallmine was discontinued in 2024', origin: 'Czech Republic', pricingPerYear: '$600', slogan: 'Make Smarter Investments' From 203ae62b650a552f4c894a5e447c3be6474bb4d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 21:01:38 +0100 Subject: [PATCH 086/157] Feature/update locales (#6049) * Update locales * Update translations --------- Co-authored-by: github-actions[bot] Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- apps/client/src/locales/messages.ca.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.de.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.es.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.fr.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.it.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.nl.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.pl.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.pt.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.tr.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.uk.xlf | 90 +++++++++++++++++++------ apps/client/src/locales/messages.xlf | 86 +++++++++++++++++------ apps/client/src/locales/messages.zh.xlf | 90 +++++++++++++++++++------ 12 files changed, 848 insertions(+), 228 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 2944e43fb..96eba410c 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -655,7 +655,7 @@ Realment vol suprimir aquest compte? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150
@@ -1398,6 +1398,14 @@ 107 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Actualitzar plataforma @@ -2015,7 +2023,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -2475,7 +2487,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2646,6 +2662,14 @@ 203 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Autenticació biomètrica @@ -3884,7 +3908,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -4844,7 +4868,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -5001,7 +5025,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5289,7 +5317,7 @@ Realment voleu eliminar el saldo d’aquest compte? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -5353,7 +5381,7 @@ De veritat vols suprimir aquestes activitats? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -5361,7 +5389,7 @@ Realment vols suprimir aquesta activitat? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -5501,7 +5529,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5576,12 +5612,20 @@ 59 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Dipòsit libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -5597,7 +5641,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -5609,7 +5653,7 @@ Estalvi libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -5849,7 +5893,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -6165,7 +6209,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -6245,11 +6289,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -6621,7 +6665,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6876,6 +6920,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7700,7 +7752,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 02ef33acd..040d12901 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -302,7 +302,7 @@ Möchtest du dieses Konto wirklich löschen? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -862,7 +862,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -1230,7 +1234,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2146,7 +2154,7 @@ Nachhaltiges Einkommen im Ruhestand apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -2302,7 +2310,7 @@ Möchtest du diese Aktivität wirklich löschen? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -2509,12 +2517,20 @@ 90 + + annual interest rate + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Einlage libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -2530,7 +2546,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -2542,7 +2558,7 @@ Ersparnisse libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -2954,7 +2970,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -2974,11 +2990,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3022,7 +3038,7 @@ Wenn du heute in den Ruhestand gehen würdest, könntest du apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -3762,7 +3778,7 @@ Möchtest du diese Aktivitäten wirklich löschen? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3773,6 +3789,14 @@ 306 + + By + Bis + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Plattform bearbeiten @@ -4557,6 +4581,14 @@ 52 + + this is projected to increase to + wird ein Anstieg prognostiziert auf + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Biometrische Authentifizierung @@ -5320,7 +5352,7 @@ und einer sicheren Entnahmerate (SWR) von apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5540,7 +5572,15 @@ entnehmen, apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5564,7 +5604,11 @@ pro Monat apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5680,7 +5724,7 @@ Möchtest du diesen Cash-Bestand wirklich löschen? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6645,7 +6689,7 @@ bezogen auf dein Gesamtanlagevermögen von apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6900,6 +6944,14 @@ 163 + + assuming a + bei einem angenommenen Jahreszinssatz von + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + to use our referral link and get a Ghostfolio Premium membership for one year um unseren Empfehlungslink zu verwenden und ein Ghostfolio Premium-Abonnement für ein Jahr zu erhalten @@ -7700,7 +7752,7 @@ Möchtest du diesen Eintrag wirklich löschen? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 4314903be..134f26302 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -303,7 +303,7 @@ ¿Estás seguro de eliminar esta cuenta? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -847,7 +847,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -1215,7 +1219,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2131,7 +2139,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -2287,7 +2295,7 @@ ¿Estás seguro de eliminar esta operación? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -2491,7 +2499,7 @@ Ahorros libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -2507,19 +2515,27 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts 40 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Depósito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -2939,7 +2955,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -2959,11 +2975,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3007,7 +3023,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -3739,7 +3755,7 @@ ¿Realmente deseas eliminar estas actividades? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3750,6 +3766,14 @@ 306 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Actualizar plataforma @@ -4534,6 +4558,14 @@ 52 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Autenticación biométrica @@ -5297,7 +5329,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5517,7 +5549,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5541,7 +5581,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5657,7 +5701,7 @@ ¿Realmente desea eliminar el saldo de esta cuenta? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6622,7 +6666,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6877,6 +6921,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7701,7 +7753,7 @@ ¿Realmente deseas eliminar este elemento? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 41dc3d391..e9eae06e1 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -358,7 +358,7 @@ Voulez-vous vraiment supprimer ce compte ? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -1118,7 +1118,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -1498,7 +1502,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2389,12 +2397,20 @@ 38 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Dépôt libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -2514,7 +2530,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -2758,7 +2774,7 @@ Voulez-vous vraiment supprimer cette activité ? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -2814,7 +2830,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -2826,7 +2842,7 @@ Épargne libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -2962,7 +2978,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -3154,7 +3170,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -3198,11 +3214,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3738,7 +3754,7 @@ Voulez-vous vraiment supprimer toutes vos activités ? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3749,6 +3765,14 @@ 306 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Mettre à jour la Plateforme @@ -4533,6 +4557,14 @@ 52 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Authentication biométrique @@ -5296,7 +5328,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5516,7 +5548,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5540,7 +5580,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5656,7 +5700,7 @@ Voulez-vous vraiment supprimer ce solde de compte ? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6621,7 +6665,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6876,6 +6920,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7700,7 +7752,7 @@ Voulez-vous vraiment supprimer cet élément? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index e5bc9ccc6..d0ae125c6 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -303,7 +303,7 @@ Vuoi davvero eliminare questo account? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -847,7 +847,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -1215,7 +1219,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2131,7 +2139,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -2287,7 +2295,7 @@ Vuoi davvero eliminare questa attività? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -2491,7 +2499,7 @@ Risparmio libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -2507,19 +2515,27 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts 40 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Deposito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -2939,7 +2955,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -2959,11 +2975,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3007,7 +3023,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -3739,7 +3755,7 @@ Vuoi davvero eliminare tutte le tue attività? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3750,6 +3766,14 @@ 306 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Aggiorna la piattaforma @@ -4534,6 +4558,14 @@ 52 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Autenticazione biometrica @@ -5297,7 +5329,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5517,7 +5549,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5541,7 +5581,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5657,7 +5701,7 @@ Vuoi veramente elimnare il saldo di questo conto? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6622,7 +6666,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6877,6 +6921,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7701,7 +7753,7 @@ Vuoi davvero eliminare questo elemento? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 5d1bec1b5..9aa33e90a 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -302,7 +302,7 @@ Wil je deze rekening echt verwijderen? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -846,7 +846,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -1214,7 +1218,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2130,7 +2138,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -2286,7 +2294,7 @@ Wil je deze activiteit echt verwijderen? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -2490,7 +2498,7 @@ Besparingen libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -2506,19 +2514,27 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts 40 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Storting libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -2938,7 +2954,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -2958,11 +2974,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3006,7 +3022,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -3738,7 +3754,7 @@ Weet je zeker dat je alle activiteiten wilt verwijderen? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3749,6 +3765,14 @@ 306 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Platform bijwerken @@ -4533,6 +4557,14 @@ 52 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Biometrische authenticatie @@ -5296,7 +5328,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5516,7 +5548,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5540,7 +5580,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5656,7 +5700,7 @@ Wilt u dit rekeningsaldo echt verwijderen? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6621,7 +6665,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6876,6 +6920,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7700,7 +7752,7 @@ Wilt u dit item echt verwijderen? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 6e167a355..8cee0ce85 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -579,7 +579,7 @@ Czy na pewno chcesz usunąć to konto? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -1226,6 +1226,14 @@ 107 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Aktualizuj platformę @@ -1711,7 +1719,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -2191,7 +2203,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2330,6 +2346,14 @@ 203 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Uwierzytelnianie Biometryczne @@ -3503,7 +3527,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -3607,7 +3631,7 @@ Czy na pewno chcesz usunąć te aktywności? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -4058,12 +4082,20 @@ 38 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Depozyt libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -4383,7 +4415,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -4516,7 +4548,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -4864,7 +4900,7 @@ Czy na pewno chcesz usunąć tę działalność? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -4884,7 +4920,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -4972,7 +5016,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -4984,7 +5028,7 @@ Oszczędności libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -5216,7 +5260,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -5524,7 +5568,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -5604,11 +5648,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5656,7 +5700,7 @@ Czy na pewno chcesz usunąć saldo tego konta? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6621,7 +6665,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6876,6 +6920,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7700,7 +7752,7 @@ Czy na pewno chcesz usunąć ten element? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 8b63acdf6..a99c8004d 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -358,7 +358,7 @@ Pretende realmente eliminar esta conta? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -917,12 +917,20 @@ 17 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Depósito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -994,7 +1002,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -1486,7 +1498,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2450,7 +2466,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -2658,7 +2674,7 @@ Deseja realmente eliminar esta atividade? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -2714,7 +2730,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -2726,7 +2742,7 @@ Poupanças libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -2806,7 +2822,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -2998,7 +3014,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -3042,11 +3058,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3738,7 +3754,7 @@ Deseja mesmo eliminar estas atividades? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3749,6 +3765,14 @@ 306 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Atualizar plataforma @@ -4533,6 +4557,14 @@ 52 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Autenticação biométrica @@ -5296,7 +5328,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5516,7 +5548,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5540,7 +5580,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5656,7 +5700,7 @@ Você realmente deseja excluir o saldo desta conta? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6621,7 +6665,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6876,6 +6920,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7700,7 +7752,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index bc687b3d4..f928823d1 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -539,7 +539,7 @@ Bu hesabı silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -1142,6 +1142,14 @@ 107 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Platformu Güncelle @@ -1571,7 +1579,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -3111,7 +3123,7 @@ Tüm işlemlerinizi silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3546,12 +3558,20 @@ 38 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Para Yatırma libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -3871,7 +3891,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -4336,7 +4356,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -4443,6 +4467,14 @@ 203 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Biyometrik Kimlik Doğrulama @@ -4584,7 +4616,7 @@ TBu işlemi silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -4664,7 +4696,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -4676,7 +4708,7 @@ Tasarruflar libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -4908,7 +4940,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -5200,7 +5232,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -5244,11 +5276,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5304,7 +5336,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -5516,7 +5548,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5540,7 +5580,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5656,7 +5700,7 @@ Bu nakit bakiyesini silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6621,7 +6665,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6876,6 +6920,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -7700,7 +7752,7 @@ Bu öğeyi silmek istediğinize emin misiniz? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 166b79a13..b64cde692 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -671,7 +671,7 @@ Ви дійсно хочете видалити цей обліковий запис? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -1386,6 +1386,14 @@ 107 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform Оновити платформу @@ -1527,7 +1535,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -2763,7 +2775,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2934,6 +2950,14 @@ 203 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication Біометрична аутентифікація @@ -4164,7 +4188,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -4968,7 +4992,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -5216,7 +5240,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -5607,7 +5631,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -5762,6 +5790,14 @@ 171 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + 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 @@ -6007,7 +6043,7 @@ Ви дійсно хочете видалити цей рахунок? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -6071,7 +6107,7 @@ Ви дійсно хочете видалити ці дії? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -6079,7 +6115,7 @@ Ви дійсно хочете видалити цю активність? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -6219,7 +6255,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -6294,12 +6338,20 @@ 59 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit Депозит libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -6315,7 +6367,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -6327,7 +6379,7 @@ Заощадження libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -6675,7 +6727,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -7015,7 +7067,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -7267,11 +7319,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -7700,7 +7752,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 92d1c9639..5f9f21b27 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -555,7 +555,7 @@ Do you really want to delete this account? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -1161,6 +1161,13 @@ 107 + + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform @@ -1601,7 +1608,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -2038,7 +2049,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2162,6 +2177,13 @@ 203 + + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication @@ -3235,7 +3257,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -3331,7 +3353,7 @@ Do you really want to delete these activities? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -3737,11 +3759,18 @@ 38 + + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -4028,7 +4057,7 @@ Sustainable retirement income apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -4146,7 +4175,11 @@ per month apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -4431,7 +4464,7 @@ Do you really want to delete this account balance? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -4481,7 +4514,7 @@ Do you really want to delete this activity? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -4513,7 +4546,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -4591,7 +4632,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -4602,7 +4643,7 @@ Savings libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -4815,7 +4856,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -5091,7 +5132,7 @@ If you retire today, you would be able to withdraw apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -5163,11 +5204,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -6079,7 +6120,7 @@ based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6259,6 +6300,13 @@ 10 + + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + to use our referral link and get a Ghostfolio Premium membership for one year @@ -6983,7 +7031,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 6a9abc040..d0fc293c0 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -588,7 +588,7 @@ 您确定要删除此账户吗? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 151 + 150 @@ -1235,6 +1235,14 @@ 107 + + By + By + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 140 + + Update platform 更新平台 @@ -1720,7 +1728,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 81 + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 162 apps/client/src/app/pages/pricing/pricing-page.html @@ -2200,7 +2212,11 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 78 + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 159 apps/client/src/app/pages/pricing/pricing-page.html @@ -2339,6 +2355,14 @@ 203 + + this is projected to increase to + this is projected to increase to + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 148 + + Biometric Authentication 生物识别认证 @@ -3512,7 +3536,7 @@ 和安全取款率 (SWR) 为 apps/client/src/app/pages/portfolio/fire/fire-page.html - 107 + 109 @@ -3616,7 +3640,7 @@ 您确定要删除这些活动吗? libs/ui/src/lib/activities-table/activities-table.component.ts - 279 + 278 @@ -4067,12 +4091,20 @@ 38 + + annual interest rate + annual interest rate + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 187 + + Deposit 存款 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 362 + 377 @@ -4392,7 +4424,7 @@ 可持续的退休收入 apps/client/src/app/pages/portfolio/fire/fire-page.html - 40 + 41 @@ -4525,7 +4557,11 @@ 每月 apps/client/src/app/pages/portfolio/fire/fire-page.html - 92 + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 173 @@ -4837,7 +4873,7 @@ 您确实要删除该帐户余额吗? libs/ui/src/lib/account-balances/account-balances.component.ts - 121 + 120 @@ -4893,7 +4929,7 @@ 您确实要删除此活动吗? libs/ui/src/lib/activities-table/activities-table.component.ts - 289 + 288 @@ -4929,7 +4965,15 @@ , apps/client/src/app/pages/portfolio/fire/fire-page.html - 93 + 95 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 146 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 @@ -5017,7 +5061,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 372 + 387 libs/ui/src/lib/i18n.ts @@ -5029,7 +5073,7 @@ 储蓄 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 382 + 397 @@ -5261,7 +5305,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 413 + 437 @@ -5569,7 +5613,7 @@ 如果您今天退休,您将能够提取 apps/client/src/app/pages/portfolio/fire/fire-page.html - 66 + 68 @@ -5649,11 +5693,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 415 + 439 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 428 + 452 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -6622,7 +6666,7 @@ 基于您总资产的 apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 + 97 @@ -6877,6 +6921,14 @@ 163 + + assuming a + assuming a + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 176 + + to use our referral link and get a Ghostfolio Premium membership for one year 使用我们的推荐链接并获得一年的Ghostfolio Premium会员资格 @@ -7701,7 +7753,7 @@ 您确定要删除此项目吗? libs/ui/src/lib/benchmark/benchmark.component.ts - 140 + 139 From b2c7f8b3adcae09404d2a87235f1ec0233895b60 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Dec 2025 08:54:22 +0100 Subject: [PATCH 087/157] Task/improve language localization for de 20251214 (#6066) * Improve language localization --- .../src/app/pages/portfolio/fire/fire-page.html | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index a4a17fa86..b441b2563 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -92,9 +92,8 @@ />   per month, -   - based on your total assets of + > + , based on your total assets of   - , + ,   this is projected to increase to   @@ -171,9 +170,8 @@ />   per month, -   - assuming a + > + , assuming a   Date: Sun, 14 Dec 2025 09:12:50 +0100 Subject: [PATCH 088/157] Feature/update locales (#6065) * Update locales * Update translations --------- Co-authored-by: github-actions[bot] Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- apps/client/src/locales/messages.ca.xlf | 42 +++++++++------------- apps/client/src/locales/messages.de.xlf | 46 ++++++++++--------------- apps/client/src/locales/messages.es.xlf | 42 +++++++++------------- apps/client/src/locales/messages.fr.xlf | 42 +++++++++------------- apps/client/src/locales/messages.it.xlf | 42 +++++++++------------- apps/client/src/locales/messages.nl.xlf | 42 +++++++++------------- apps/client/src/locales/messages.pl.xlf | 42 +++++++++------------- apps/client/src/locales/messages.pt.xlf | 42 +++++++++------------- apps/client/src/locales/messages.tr.xlf | 42 +++++++++------------- apps/client/src/locales/messages.uk.xlf | 42 +++++++++------------- apps/client/src/locales/messages.xlf | 38 ++++++++------------ apps/client/src/locales/messages.zh.xlf | 42 +++++++++------------- 12 files changed, 204 insertions(+), 300 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 96eba410c..4b86b37ba 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -1403,7 +1403,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -2027,7 +2027,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -2491,7 +2491,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2667,7 +2667,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -3908,7 +3908,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5029,7 +5029,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -5524,20 +5524,12 @@ 61 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5617,7 +5609,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -6660,12 +6652,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6920,12 +6912,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 040d12901..60490492f 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -866,7 +866,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -1238,7 +1238,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2519,10 +2519,10 @@ annual interest rate - + apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -3794,7 +3794,7 @@ Bis apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -4586,7 +4586,7 @@ wird ein Anstieg prognostiziert auf apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5352,7 +5352,7 @@ und einer sicheren Entnahmerate (SWR) von apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5567,20 +5567,12 @@ 5 - + , - entnehmen, - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - + apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5608,7 +5600,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6684,12 +6676,12 @@ 178 - - based on your total assets of - bezogen auf dein Gesamtanlagevermögen von + + , based on your total assets of + entnehmen, bezogen auf dein Gesamtanlagevermögen von apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6944,12 +6936,12 @@ 163 - - assuming a - bei einem angenommenen Jahreszinssatz von + + , assuming a + , bei einem angenommenen Jahreszinssatz von apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 134f26302..c7aef0b07 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -851,7 +851,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -1223,7 +1223,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2527,7 +2527,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -3771,7 +3771,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -4563,7 +4563,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5329,7 +5329,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5544,20 +5544,12 @@ 5 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5585,7 +5577,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6661,12 +6653,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6921,12 +6913,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index e9eae06e1..8f6179681 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -1122,7 +1122,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -1506,7 +1506,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2402,7 +2402,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -3770,7 +3770,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -4562,7 +4562,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5328,7 +5328,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5543,20 +5543,12 @@ 5 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5584,7 +5576,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6660,12 +6652,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6920,12 +6912,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index d0ae125c6..a32ed3165 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -851,7 +851,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -1223,7 +1223,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2527,7 +2527,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -3771,7 +3771,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -4563,7 +4563,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5329,7 +5329,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5544,20 +5544,12 @@ 5 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5585,7 +5577,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6661,12 +6653,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6921,12 +6913,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 9aa33e90a..e3a289e20 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -850,7 +850,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -1222,7 +1222,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2526,7 +2526,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -3770,7 +3770,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -4562,7 +4562,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5328,7 +5328,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5543,20 +5543,12 @@ 5 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5584,7 +5576,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6660,12 +6652,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6920,12 +6912,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 8cee0ce85..735aab38e 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -1231,7 +1231,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -1723,7 +1723,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -2207,7 +2207,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2351,7 +2351,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -3527,7 +3527,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -4087,7 +4087,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -4552,7 +4552,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -4915,20 +4915,12 @@ 140 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -6660,12 +6652,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6920,12 +6912,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index a99c8004d..999f23c98 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -922,7 +922,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -1006,7 +1006,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -1502,7 +1502,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -3770,7 +3770,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -4562,7 +4562,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5328,7 +5328,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5543,20 +5543,12 @@ 5 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5584,7 +5576,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6660,12 +6652,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6920,12 +6912,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index f928823d1..91f695a20 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -1147,7 +1147,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -1583,7 +1583,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -3563,7 +3563,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -4360,7 +4360,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -4472,7 +4472,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -5336,7 +5336,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -5543,20 +5543,12 @@ 5 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -5584,7 +5576,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -6660,12 +6652,12 @@ 178 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6920,12 +6912,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index b64cde692..c7c1b195e 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -1391,7 +1391,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -1539,7 +1539,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -2779,7 +2779,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2955,7 +2955,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -4188,7 +4188,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -4987,12 +4987,12 @@ 58 - - based on your total assets of - based on your total assets of + + , based on your total assets of + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -5635,7 +5635,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -5790,12 +5790,12 @@ 171 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 @@ -6250,20 +6250,12 @@ 61 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -6343,7 +6335,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 5f9f21b27..b1539bb8a 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -1165,7 +1165,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -1612,7 +1612,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -2053,7 +2053,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2181,7 +2181,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -3257,7 +3257,7 @@ and a safe withdrawal rate (SWR) of apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -3763,7 +3763,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -4179,7 +4179,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -4542,19 +4542,11 @@ 61 - + , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -6116,11 +6108,11 @@ 34 - - based on your total assets of + + , based on your total assets of apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6300,11 +6292,11 @@ 10 - - assuming a + + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index d0fc293c0..17b7f26ea 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -1240,7 +1240,7 @@ By apps/client/src/app/pages/portfolio/fire/fire-page.html - 140 + 139 @@ -1732,7 +1732,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 162 + 161 apps/client/src/app/pages/pricing/pricing-page.html @@ -2216,7 +2216,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 159 + 158 apps/client/src/app/pages/pricing/pricing-page.html @@ -2360,7 +2360,7 @@ this is projected to increase to apps/client/src/app/pages/portfolio/fire/fire-page.html - 148 + 147 @@ -3536,7 +3536,7 @@ 和安全取款率 (SWR) 为 apps/client/src/app/pages/portfolio/fire/fire-page.html - 109 + 108 @@ -4096,7 +4096,7 @@ annual interest rate apps/client/src/app/pages/portfolio/fire/fire-page.html - 187 + 185 @@ -4561,7 +4561,7 @@ apps/client/src/app/pages/portfolio/fire/fire-page.html - 173 + 172 @@ -4960,20 +4960,12 @@ 61 - + , , apps/client/src/app/pages/portfolio/fire/fire-page.html - 95 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 146 - - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 174 + 145 @@ -6661,12 +6653,12 @@ 178 - - based on your total assets of - 基于您总资产的 + + , based on your total assets of + 基于您总资产的 apps/client/src/app/pages/portfolio/fire/fire-page.html - 97 + 96 @@ -6921,12 +6913,12 @@ 163 - - assuming a - assuming a + + , assuming a + , assuming a apps/client/src/app/pages/portfolio/fire/fire-page.html - 176 + 174 From 388dcd208fa954fb7d630b88007e68659391efd4 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 14 Dec 2025 09:14:57 +0100 Subject: [PATCH 089/157] Release 2.223.0 (#6067) --- 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 dab53a614..ec2acb0c9 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 +## 2.223.0 - 2025-12-14 ### Added diff --git a/package-lock.json b/package-lock.json index 61f38db9f..87db87109 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.222.0", + "version": "2.223.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.222.0", + "version": "2.223.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index d0ee2c084..30989f304 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.222.0", + "version": "2.223.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 2feef3a17ef7cad4cd654e687aee289703fb9af3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 16 Dec 2025 08:23:57 +0100 Subject: [PATCH 090/157] Task/refresh cryptocurrencies list 20251215 (#6073) * Update cryptocurrencies.json * Update changelog --- CHANGELOG.md | 6 + .../cryptocurrencies/cryptocurrencies.json | 182 +++++++++++++++--- 2 files changed, 160 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2acb0c9..c0970b888 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 + +- Refreshed the cryptocurrencies list + ## 2.223.0 - 2025-12-14 ### Added diff --git a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json index 4bd7794ca..80c07fc64 100644 --- a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json +++ b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json @@ -119,6 +119,7 @@ "3ULL": "3ULL Coin", "3ULLV1": "Playa3ull Games v1", "3XD": "3DChain", + "401JK": "401jk", "404A": "404Aliens", "404BLOCKS": "404Blocks", "420CHAN": "420chan", @@ -397,7 +398,7 @@ "AGN": "Agnus Ai", "AGNT": "iAgent Protocol", "AGO": "AgoDefi", - "AGON": "Arabian Dragon", + "AGON": "AGON Agent", "AGORK": "@gork", "AGOV": "Answer Governance", "AGPC": "AGPC", @@ -468,6 +469,7 @@ "AII": "Artificial Idiot", "AIINU": "AI INU", "AIKEK": "AlphaKEK.AI", + "AILAYER": "AILayer", "AILINK": "AiLink Token", "AIM": "ModiHost", "AIMAGA": "Presidentexe", @@ -634,6 +636,7 @@ "ALLO": "Allora", "ALM": "Alium Finance", "ALMAN": "Alman", + "ALMANAK": "Almanak", "ALMC": "Awkward Look Monkey Club", "ALME": "Alita", "ALMOND": "Almond", @@ -904,6 +907,7 @@ "AR": "Arweave", "ARA": "Ara Token", "ARABCLUB": "The Arab Club Token", + "ARABIANDRAGON": "Arabian Dragon", "ARACOIN": "Ara", "ARARA": "Araracoin", "ARATA": "Arata", @@ -944,7 +948,8 @@ "ARE": "Aurei", "AREA": "Areon Network", "AREN": "Arenon", - "ARENA": "Arena", + "ARENA": "Alpha Arena", + "ARENAT": "ArenaToken", "AREPA": "Arepacoin", "ARES": "ARES", "ARESP": "Ares Protocol", @@ -958,9 +963,11 @@ "ARIA": "ARIA.AI", "ARIA20": "Arianee", "ARIAIP": "Aria", + "ARIO": "AR.IO Network", "ARIT": "ArithFi", "ARIX": "Arix", "ARK": "ARK", + "ARKDEFAI": "ARK", "ARKEN": "Arken Finance", "ARKER": "Arker", "ARKI": "ArkiTech", @@ -985,6 +992,7 @@ "ARON": "Astronaut Aron", "AROR": "Arora", "AROS": "Aros", + "AROX": "OFFICIAL AROX", "ARPA": "ARPA Chain", "ARPAC": "ArpaCoin", "ARQ": "ArQmA", @@ -1019,6 +1027,7 @@ "ARTP": "ArtPro", "ARTR": "Artery Network", "ARTT": "ARTT Network", + "ARTX": "Ultiland", "ARTY": "Artyfact", "ARV": "Ariva", "ARW": "Arowana Token", @@ -1030,6 +1039,7 @@ "ASAN": "ASAN VERSE", "ASAP": "Asap Sniper Bot", "ASBNB": "Astherus Staked BNB", + "ASC": "All InX SMART CHAIN", "ASCEND": "Ascend", "ASD": "AscendEX Token", "ASDEX": "AstraDEX", @@ -1153,6 +1163,7 @@ "ATS": "Alltoscan", "ATT": "Attila", "ATTR": "Attrace", + "ATTRA": "Attractor", "ATU": "Quantum", "ATX": "ArtexCoin", "AU": "AutoCrypto", @@ -1164,6 +1175,7 @@ "AUDC": "Aussie Digital", "AUDF": "Forte AUD", "AUDIO": "Audius", + "AUDM": "Macropod Stablecoin", "AUDT": "Auditchain", "AUDX": "eToro Australian Dollar", "AUK": "Aukcecoin", @@ -1290,6 +1302,7 @@ "AXT": "AIX", "AXYS": "Axys", "AYA": "Aryacoin", + "AYNI": "Ayni Gold", "AZ": "Azbit", "AZA": "Kaliza", "AZART": "Azart", @@ -1327,7 +1340,6 @@ "BABI": "Babylons", "BABL": "Babylon Finance", "BABY": "Babylon", - "BABY4": "Baby 4", "BABYANDY": "Baby Andy", "BABYASTER": "Baby Aster", "BABYB": "Baby Bali", @@ -1506,6 +1518,7 @@ "BANDO": "Bandot", "BANG": "BANG", "BANGY": "BANGY", + "BANK": "Lorenzo Protocol", "BANKA": "Bank AI", "BANKBRC": "BANK Ordinals", "BANKC": "Bankcoin", @@ -1626,6 +1639,7 @@ "BBYDEV": "The Dev is a Baby", "BC": "Blood Crystal", "BC3M": "Backed GOVIES 0-6 Months Euro Investment Grade", + "BC400": "Bitcoin Cultivator 400", "BCA": "Bitcoin Atom", "BCAC": "Business Credit Alliance Chain", "BCAI": "Bright Crypto Ai", @@ -1673,7 +1687,7 @@ "BCRO": "Bonded Cronos", "BCS": "Business Credit Substitute", "BCSPX": "Backed CSPX Core S&P 500", - "BCT": "Toucan Protocol: Base Carbon Tonne", + "BCT": "Buy Coin Token", "BCUBE": "B-cube.ai", "BCUG": "Blockchain Cuties Universe Governance", "BCUT": "bitsCrunch", @@ -1723,6 +1737,7 @@ "BEATS": "Sol Beats", "BEATTOKEN": "BEAT Token", "BEAVER": "beaver", + "BEB1M": "BeB", "BEBE": "BEBE", "BEBEETH": "BEBE", "BEBEV1": "BEBE v1", @@ -1845,6 +1860,7 @@ "BFX": "BitFinex Tokens", "BG": "BunnyPark Game", "BGB": "Bitget token", + "BGBG": "BigMouthFrog", "BGBP": "Binance GBP Stable Coin", "BGBV1": "Bitget Token v1", "BGC": "Bee Token", @@ -1876,12 +1892,14 @@ "BHO": "Bholdus Token", "BHP": "Blockchain of Hash Power", "BHPC": "BHPCash", + "BIAFRA": "Biafra Coin", "BIAO": "BIAO", "BIAOCOIN": "Biaocoin", "BIB": "BIB Token", "BIB01": "Backed IB01 $ Treasury Bond 0-1yr", - "BIBI": "BIBI", + "BIBI": "Binance bibi", "BIBI2025": "Bibi", + "BIBIBSC": "BIBI", "BIBL": "Biblecoin", "BIBO": "Bible of Memes", "BIBTA": "Backed IBTA $ Treasury Bond 1-3yr", @@ -2012,6 +2030,7 @@ "BITGRIN": "BitGrin", "BITHER": "Bither", "BITL": "BitLux", + "BITLAYER": "Bitlayer", "BITM": "BitMoney", "BITN": "Bitnet", "BITNEW": "BitNewChain", @@ -2146,6 +2165,7 @@ "BLOGGE": "Bloggercube", "BLOK": "Bloktopia", "BLOO": "bloo foster coin", + "BLOOCYS": "BlooCYS", "BLOODY": "Bloody Token", "BLOOM": "BloomBeans", "BLOOMT": "Bloom Token", @@ -2297,7 +2317,8 @@ "BOAM": "BOOK OF AI MEOW", "BOARD": "SurfBoard Finance", "BOAT": "Doubloon", - "BOATKID": "BoatKid", + "BOATKID": "Pacu Jalur", + "BOATKIDSITE": "BoatKid", "BOBA": "Boba Network", "BOBAI": "Bob AI", "BOBAOPPA": "Bobaoppa", @@ -2307,6 +2328,7 @@ "BOBE": "BOOK OF BILLIONAIRES", "BOBER": "BOBER", "BOBFUN": "BOB", + "BOBL2": "BOB", "BOBLS": "Boblles", "BOBMARLEY": "Bob Marley Meme", "BOBO": "BOBO", @@ -2444,6 +2466,7 @@ "BORKIE": "Borkie", "BORPA": "Borpa", "BORUTO": "Boruto Inu", + "BOS": "BitcoinOS Token", "BOSCOIN": "BOScoin", "BOSE": "Bitbose", "BOSHI": "Boshi", @@ -2566,6 +2589,7 @@ "BRIA": "Briacoin", "BRIAN": "Brian Arm Strong", "BRIANWIF": "Brianwifhat", + "BRIBE": "Bribe Protocol", "BRIC": "Redbrick", "BRICK": "Brickchain FInance", "BRICKS": "MyBricks", @@ -2587,6 +2611,8 @@ "BRKBX": "Berkshire Hathaway xStock", "BRKL": "Brokoli Token", "BRL1": "BRL1", + "BRLV": "High Velocity BRLY", + "BRLY": "Yield Bearing BRL", "BRM": "BullRun Meme", "BRMV": "BRMV Token", "BRN": "BRN Metaverse", @@ -2702,6 +2728,7 @@ "BTC": "Bitcoin", "BTC2": "Bitcoin 2", "BTC2XFLI": "BTC 2x Flexible Leverage Index", + "BTC6900": "Bitcoin 6900", "BTC70000": "BTC 70000", "BTCA": "BITCOIN ADDITIONAL", "BTCAB": "Bitcoin Avalanche Bridged", @@ -2860,7 +2887,8 @@ "BULLIEVERSE": "Bullieverse", "BULLINU": "Bull inu", "BULLIONFX": "BullionFX", - "BULLISH": "bullish", + "BULLISH": "Bullish Degen", + "BULLISHCOIN": "bullish", "BULLMOON": "Bull Moon", "BULLPEPE": "Bull Pepe", "BULLPEPEIO": "Bullpepe", @@ -3023,11 +3051,13 @@ "CALLISTO": "Callisto Network", "CALLS": "OnlyCalls by Virtuals", "CALO": "Calo", + "CALVIN": "CALVIN", "CAM": "Consumption Avatar Matrix", "CAMC": "Camcoin", "CAMEL": "The Camel", "CAMINO": "Camino Network", "CAMLY": "Camly Coin", + "CAMP": "Camp Network", "CAMPGLOBAL": "Camp", "CAMT": "CAMELL", "CAN": "Channels", @@ -3246,6 +3276,7 @@ "CDCETH": "Crypto.com Staked ETH", "CDCSOL": "Crypto.com Staked SOL", "CDEX": "Cryptodex", + "CDG": "CDG Project", "CDL": "Creditlink", "CDN": "Canada eCoin", "CDOG": "Corn Dog", @@ -3340,6 +3371,7 @@ "CHAINSOFWAR": "Chains of War", "CHAL": "Chalice Finance", "CHAM": "Champion", + "CHAMP": "Super Champs", "CHAMPZ": "Champz", "CHAN": "ChanCoin", "CHANCE": "Ante Casino", @@ -3351,6 +3383,7 @@ "CHAPZ": "Chappyz", "CHARGED": "GoCharge Tech", "CHARIZARD": "Charizard Inu", + "CHARL": "Charlie", "CHARLIE": "Charlie Kirk", "CHARM": "Charm Coin", "CHARS": "CHARS", @@ -3367,7 +3400,7 @@ "CHBR": "CryptoHub", "CHC": "ChainCoin", "CHD": "CharityDAO", - "CHECK": "Paycheck", + "CHECK": "Checkmate", "CHECKR": "CheckerChain", "CHECOIN": "CheCoin", "CHED": "Giggleched", @@ -3450,6 +3483,7 @@ "CHONK": "Chonk", "CHONKY": "CHONKY", "CHOO": "Chooky", + "CHOOCH": "CHOOCH", "CHOOF": "ChoofCoin", "CHOPPER": "Chopper Inu", "CHOPPY": "Choppy", @@ -3697,6 +3731,7 @@ "COINDEALTOKEN": "CoinDeal Token", "COINDEFI": "Coin", "COINDEPO": "CoinDepo Token", + "COINEDELWEIS": "Coin Edelweis", "COING": "Coingrid", "COINH": "Coinhound", "COINLION": "CoinLion", @@ -3853,6 +3888,7 @@ "CPIGGY": "Vix Finance", "CPL": "CoinPlace Token", "CPLO": "Cpollo", + "CPM": "Crypto Pump Meme", "CPN": "CompuCoin", "CPO": "Cryptopolis", "CPOO": "Cockapoo", @@ -4243,7 +4279,7 @@ "CYPR": "Cypher", "CYRS": "Cyrus Token", "CYRUS": "Cyrus Exchange", - "CYS": "BlooCYS", + "CYS": "Cysic", "CYT": "Cryptokenz", "CZ": "CHANGPENG ZHAO (changpengzhao.club)", "CZBOOK": "CZ BOOK", @@ -4319,6 +4355,7 @@ "DANGEL": "dAngel Fund", "DANJ": "Danjuan Cat", "DANK": "DarkKush", + "DANKDOGE": "Dank Doge", "DANNY": "Degen Danny", "DAO": "DAO Maker", "DAO1": "DAO1", @@ -4560,6 +4597,7 @@ "DEOD": "Decentrawood", "DEOR": "Decentralized Oracle", "DEP": "DEAPCOIN", + "DEPAY": "DePay", "DEPIN": "DEPIN", "DEPINU": "Depression Inu", "DEPO": "Depo", @@ -4710,8 +4748,9 @@ "DIGEX": "Digex", "DIGG": "DIGG", "DIGGAI": "DIGGER AI", - "DIGI": "Digicoin", + "DIGI": "MineD", "DIGIC": "DigiCube", + "DIGICOIN": "Digicoin", "DIGIF": "DigiFel", "DIGIMON": "Digimon", "DIGIMONRABBIT": "Digimon Rabbit", @@ -4875,6 +4914,7 @@ "DOGBOSS": "Dog Boss", "DOGC": "Dogeclub", "DOGCOIN": "Dogcoin", + "DOGCOLLAR": "Dog Collar", "DOGDEFI": "DogDeFiCoin", "DOGE": "Dogecoin", "DOGE1SAT": "DOGE-1SATELLITE", @@ -5036,6 +5076,7 @@ "DOVU": "DOVU", "DOWS": "Shadows", "DOYOUR": "Do Your Own Research", + "DOYR": "DOYR", "DP": "DigitalPrice", "DPAD": "Dpad Finance", "DPAY": "Devour", @@ -5382,7 +5423,7 @@ "EDDA": "EDDASwap", "EDDIE": "Eddie coin", "EDE": "El Dorado Exchange", - "EDEL": "Coin Edelweis", + "EDEL": "Edel", "EDEN": "Eden Token", "EDENA": "EDENA", "EDENNETWORK": "EDEN", @@ -5391,6 +5432,7 @@ "EDG": "Edgeless", "EDGE": "Definitive", "EDGEACTIVITY": "EDGE Activity Token", + "EDGEAI": "EdgeAI", "EDGEN": "LayerEdge", "EDGENET": "EDGE", "EDGESOL": "Edgevana Staked SOL", @@ -5776,6 +5818,7 @@ "ETH": "Ethereum", "ETH2": "Eth 2.0 Staking by Pool-X", "ETH2X-FLI": "ETH 2x Flexible Leverage Index", + "ETH6900": "ETH6900", "ETHA": "ETHA Lend", "ETHAX": "ETHAX", "ETHB": "ETHEREUM ON BASE", @@ -6121,6 +6164,7 @@ "FELIS": "Felis", "FELIX": "FelixCoin", "FELIX2": "Felix 2.0 ETH", + "FELY": "Felysyum", "FEN": "First Ever NFT", "FENE": "Fenerbahçe Token", "FENOMY": "Fenomy", @@ -6350,6 +6394,7 @@ "FLVR": "FlavorCoin", "FLX": "Reflexer Ungovernance Token", "FLY": "Fly.trade", + "FLYBNB": "FlyBNB", "FLYCOIN": "FlyCoin", "FLZ": "Fellaz", "FM": "Full Moon", @@ -6393,7 +6438,8 @@ "FOFAR": "FoFar", "FOFARBASE": "FOFAR", "FOFARIO": "Fofar", - "FOFO": "FOFO Token", + "FOFO": "FOFO", + "FOFOTOKEN": "FOFO Token", "FOGE": "Fat Doge", "FOIN": "Foin", "FOL": "Folder Protocol", @@ -6709,6 +6755,7 @@ "GAINFY": "Gainfy", "GAINS": "Gains", "GAINSV1": "Gains v1", + "GAIX": "GaiAI Token", "GAJ": "Gaj Finance", "GAKH": "GAKHcoin", "GAL": "Galxe", @@ -6751,6 +6798,7 @@ "GAMET": "GAME Token", "GAMETA": "Gameta", "GAMEX": "GameX", + "GAMEXCOIN": "Game X Coin", "GAMI": "GAMI World", "GAMIN": "Gaming Stars", "GAMINGDOGE": "GAMINGDOGE", @@ -6868,10 +6916,12 @@ "GEMI": "Gemini Inu", "GEMINI": "Gemini Ai", "GEMINIT": "Gemini", + "GEMO": "Gemo", "GEMS": "Gems VIP", "GEMSTON": "GEMSTON", "GEMZ": "Gemz Social", "GEN": "DAOstack", + "GENAI": "Gen AI BOT", "GENE": "Genopets", "GENECTO": "Gene", "GENESIS": "Genesis Worlds", @@ -6930,6 +6980,7 @@ "GG": "Reboot", "GGAVAX": "GoGoPool AVAX", "GGB": "GGEBI", + "GGBR": "Goldfish", "GGC": "Global Game Coin", "GGCM": "Gold Guaranteed Coin", "GGEZ1": "GGEZ1", @@ -7054,11 +7105,13 @@ "GLN": "Galion Token", "GLO": "Global Innovation Platform", "GLOBAL": "GlobalCoin", + "GLOBALTOUR": "Global Tour Club", "GLOBE": "Global", "GLORP": "Glorp", "GLORY": "SEKAI GLORY", "GLOS": "GLOS", "GLOWSHA": "GlowShares", + "GLP1": "GLP1", "GLQ": "GraphLinq Protocol", "GLR": "Glory Finance", "GLS": "Glacier", @@ -7090,6 +7143,7 @@ "GMMT": "Giant Mammoth", "GMNG": "Global Gaming", "GMNT": "Gmining", + "GMON": "gMON", "GMPD": "GamesPad", "GMR": "GAMER", "GMRT": "Gamertag Token", @@ -7300,6 +7354,7 @@ "GREG": "greg", "GRELF": "GRELF", "GREMLY": "Gremly", + "GREMLYART": "Gremly", "GREXIT": "GrexitCoin", "GREY": "Grey Token", "GRFT": "Graft Blockchain", @@ -7420,6 +7475,7 @@ "GTY": "G-Agents AI", "GUA": "GUA", "GUAC": "Guacamole", + "GUAMEME": "GUA", "GUAN": "Guanciale by Virtuals", "GUAP": "Guapcoin", "GUAR": "Guarium", @@ -7901,6 +7957,7 @@ "HPN": "HyperonChain", "HPO": "Hippocrat", "HPOWSB10I": "HarryPotterObamaWallStreetBets10Inu", + "HPP": "House Party Protocol", "HPT": "Huobi Pool Token", "HPX": "HUPAYX", "HPY": "Hyper Pay", @@ -8003,6 +8060,7 @@ "HXT": "HextraCoin", "HXX": "HexxCoin", "HXXH": "Pioneering D. UTXO-Based NFT Social Protocol", + "HYB": "Hybrid Block", "HYBN": "Hey Bitcoin", "HYBRID": "Hybrid Bank Cash", "HYBUX": "HYBUX", @@ -8225,6 +8283,7 @@ "INCORGNITO": "Incorgnito", "INCP": "InceptionCoin", "INCREMENTUM": "Incrementum", + "INCX": "INCX Coin", "IND": "Indorse", "INDAY": "Independence Day", "INDEPENDENCEDAY": "Independence Day", @@ -8249,6 +8308,7 @@ "INFC": "Influence Chain", "INFI": "Infinite", "INFINI": "Infinity Economics", + "INFINITUS": "InfinitusTokens", "INFLR": "Inflr", "INFO": "Infomatix", "INFOFI": "WAGMI HUB", @@ -8332,6 +8392,7 @@ "IOP": "Internet of People", "IOSHIB": "IoTexShiba", "IOST": "IOS token", + "IOSTV1": "IOSToken V1", "IOT": "Helium IOT", "IOTAI": "IoTAI", "IOTW": "IOTW", @@ -8379,6 +8440,7 @@ "IRT": "Infinity Rocket", "IRWA": "IncomRWA", "IRYDE": "iRYDE COIN", + "IRYS": "Irys", "ISA": "Islander", "ISDT": "ISTARDUST", "ISEC": "IntelliSecure Systems", @@ -8533,6 +8595,7 @@ "JERRYINU": "JERRYINU", "JERRYINUCOM": "Jerry Inu", "JES": "Jesus", + "JESSE": "jesse", "JEST": "Jester", "JESUS": "Jesus Coin", "JET": "Jet Protocol", @@ -8697,7 +8760,7 @@ "KABOSU": "Kabosu Family", "KABY": "Kaby Arena", "KAC": "KACO Finance", - "KACY": "Kassandra", + "KACY": "markkacy", "KADYROV": "Ramzan", "KAF": "KAIF Platform", "KAG": "Silver", @@ -8758,6 +8821,7 @@ "KASHIN": "KASHIN", "KASPER": "Kasper the ghost of Kaspa", "KASPY": "KASPY", + "KASSANDRA": "Kassandra", "KASSIAHOME": "Kassia Home", "KASTA": "Kasta", "KASTER": "King Aster", @@ -8837,6 +8901,7 @@ "KERMIT": "KermitTheCoin", "KERN": "Kernel", "KERNEL": "KernelDAO", + "KEROSENE": "Kerosene", "KET": "Ket", "KETAMINE": "Ketamine", "KETAN": "Ketan", @@ -9004,6 +9069,7 @@ "KNS": "Kenshi", "KNT": "Knekted", "KNTO": "Kento", + "KNTQ": "Kinetiq Governance Token", "KNU": "Keanu", "KNUT": "Knut From Zoo", "KNUXX": "Knuxx Bully of ETH", @@ -9096,6 +9162,7 @@ "KRT": "TerraKRW", "KRU": "Kingaru", "KRUGERCOIN": "KrugerCoin", + "KRWQ": "KRWQ", "KRX": "RAVN Korrax", "KRY": "Krypdraw", "KRYP": "Krypto Trump", @@ -9315,6 +9382,7 @@ "LEAN": "Lean Management", "LEASH": "Doge Killer", "LED": "LEDGIS", + "LEDGER": "Ledger Ai", "LEDU": "Education Ecosystem", "LEE": "Love Earn Enjoy", "LEET": "LeetSwap", @@ -9460,7 +9528,6 @@ "LINGO": "Lingo", "LINK": "Chainlink", "LINKA": "LINKA", - "LINKC": "LINKCHAIN", "LINKCHAIN": "LINK", "LINKFI": "LinkFi", "LINQ": "LINQ", @@ -9776,10 +9843,11 @@ "LUSH": "Lush AI", "LUT": "Cinemadrom", "LUTETIUM": "Lutetium Coin", - "LUX": "Lux Token", + "LUX": "Luxxcoin", "LUXAI": "Lux Token", "LUXCOIN": "LUXCoin", "LUXO": "Luxo", + "LUXTOKEN": "Lux Token", "LUXU": "Luxury Travel Token", "LUXY": "Luxy", "LVG": "Leverage Coin", @@ -9902,6 +9970,7 @@ "MAJO": "Majo", "MAJOR": "Major", "MAK": "MetaCene", + "MAKA": "MAKA", "MAKE": "MAKE", "MAKEA": "Make America Healthy Again", "MAKEE": "Make Ethereum Great Again", @@ -9938,7 +10007,8 @@ "MANUSAI": "Manus AI Agent", "MANYU": "Manyu", "MANYUDOG": "MANYU", - "MAO": "Mao", + "MAO": "MAO", + "MAOMEME": "Mao", "MAOW": "MAOW", "MAP": "MAP Protocol", "MAPC": "MapCoin", @@ -10400,6 +10470,7 @@ "MIDLE": "Midle", "MIDN": "Midnight", "MIDNIGHT": "Midnight", + "MIDNIGHTVIP": "Midnight", "MIE": "MIE Network", "MIF": "monkeywifhat", "MIG": "Migranet", @@ -10518,6 +10589,7 @@ "MITO": "Mitosis", "MITTENS": "Mittens", "MITX": "Morpheus Infrastructure Token", + "MIU": "MIU", "MIUONSOL": "Miu", "MIV": "MakeItViral", "MIVA": "Minerva Wallet", @@ -10543,6 +10615,7 @@ "MLC": "My Lovely Planet", "MLD": "MonoLend", "MLEO": "LEO Token (Multichain)", + "MLG": "360noscope420blazeit", "MLGC": "Marshal Lion Group Coin", "MLINK": "Chainlink (Multichain)", "MLITE": "MeLite", @@ -10646,6 +10719,7 @@ "MOCA": "Moca Coin", "MOCHI": "Mochiswap", "MOCHICAT": "MochiCat", + "MOCHIINU": "Mochi Inu", "MOCK": "Mock Capital", "MOCO": "MoCo", "MOD": "Modefi", @@ -10698,9 +10772,8 @@ "MOMO": "Momo", "MOMO2": "MOMO 2.0", "MOMO2025": "momo", - "MON": "MON Protocol", + "MON": "Monad", "MONA": "MonaCoin", - "MONAD": "Monad", "MONAI": "MONAI", "MONAIZE": "Monaize", "MONARCH": "TRUEMONARCH", @@ -10735,6 +10808,7 @@ "MONOLITH": "Monolith", "MONONOKEINU": "Mononoke Inu", "MONOPOLY": "Meta Monopoly", + "MONPRO": "MON Protocol", "MONS": "Monsters Clan", "MONST": "Monstock", "MONSTA": "Cake Monster", @@ -10783,6 +10857,7 @@ "MOONSTAR": "MoonStar", "MOONW": "moonwolf.io", "MOOO": "Hashtagger", + "MOOR": "MOOR TOKEN", "MOOV": "dotmoovs", "MOOX": "Moox Protocol", "MOOXV1": "Moox Protocol v1", @@ -10989,6 +11064,7 @@ "MULTIWALLET": "MultiWallet Coin", "MUMU": "Mumu", "MUN": "MUNcoin", + "MUNCAT": "MUNCAT", "MUNCH": "Munch Token", "MUNCHY": "Boys Club Munchy", "MUNDI": "Salvator Mundi", @@ -11536,6 +11612,7 @@ "NPICK": "NPICK BLOCK", "NPLC": "Plus Coin", "NPM": "Neptune Mutual", + "NPRO": "NPRO", "NPT": "Neopin", "NPTX": "NeptuneX", "NPX": "Napoleon X", @@ -11619,6 +11696,7 @@ "NUTC": "Nutcash", "NUTGV2": "NUTGAIN", "NUTS": "Thetanuts Finance", + "NUTSDAO": "NutsDAO", "NUTZ": "NUTZ", "NUUM": "MNet", "NUX": "Peanut", @@ -11754,6 +11832,8 @@ "ODS": "Odesis", "ODX": "ODX Token", "ODYS": "OdysseyWallet", + "OETHER": "Origin Ether", + "OEX": "OEX", "OF": "OFCOIN", "OFBC": "OneFinBank Coin", "OFC": "$OFC Coin", @@ -11937,6 +12017,7 @@ "OPENVC": "OpenVoiceCoin", "OPENW": "OpenWorld", "OPENX": "OpenxAI", + "OPENXSTOCK": "OPEN xStock", "OPEPE": "Optimism PEPE", "OPERATOR": "OpenAI Agent", "OPES": "Opes", @@ -11973,7 +12054,8 @@ "OPV": "OpenLive NFT", "OPXVEVELO": "OpenX Locked Velo", "ORA": "ORA Coin", - "ORACLE": "Oracle AI", + "ORACLE": "oracle", + "ORACLEAI": "Oracle AI", "ORACLECHAIN": "OracleChain", "ORACLER": "Oracler", "ORACOLXOR": "Oracolxor", @@ -12162,6 +12244,7 @@ "PALLA": "Pallapay", "PALM": "PaLM AI", "PALMECO": "Palm Economy", + "PALMO": "ORCIB", "PALMP": "PalmPay", "PALMV1": "PaLM AI v1", "PALMY": "Palmy", @@ -12186,6 +12269,7 @@ "PAO": "South Pao", "PAPA": "Papa Bear", "PAPADOGE": "Papa Doge", + "PAPARAZZI": "Paparazzi Token", "PAPAT": "PAPA Trump", "PAPER": "Dope Wars Paper", "PAPERBASE": "Paper", @@ -12251,6 +12335,7 @@ "PAYAI": "PayAI Network", "PAYB": "Paybswap", "PAYCENT": "Paycent", + "PAYCHECK": "Paycheck", "PAYCON": "Paycon", "PAYD": "PAYD", "PAYN": "PayNet Coin", @@ -12327,6 +12412,7 @@ "PEAR": "Pear Swap", "PEARL": "Pearl Finance", "PEAS": "Peapods Finance", + "PEBBLE": "Etherrock #72", "PEBIRD": "PEPE BIRD", "PEC": "PeaceCoin", "PECH": "PEPE CASH", @@ -12380,6 +12466,7 @@ "PEON": "Peon", "PEOPLE": "ConstitutionDAO", "PEOPLEFB": "PEOPLE", + "PEOS": "EOS (pTokens)", "PEOSONE": "pEOS", "PEP": "Pepechain", "PEPA": "Pepa Inu", @@ -12695,6 +12782,7 @@ "PLEO": "Empleos", "PLERF": "Plerf", "PLEX": "PLEX", + "PLEXCOIN": "PlexCoin", "PLF": "PlayFuel", "PLG": "Pledgecamp", "PLGR": "Pledge Finance", @@ -12731,7 +12819,7 @@ "PLURA": "PluraCoin", "PLUS1": "PlusOneCoin", "PLUTUS": "PlutusDAO", - "PLX": "PlexCoin", + "PLX": "Planet Labs xStock", "PLXY": "Plxyer", "PLY": "Aurigami", "PLYR": "PLYR L1", @@ -12886,6 +12974,7 @@ "PORNROCKET": "PornRocket", "PORT": "Port Finance", "PORT3": "Port3 Network", + "PORT3V2": "Port3 Network v2", "PORTAL": "Portal", "PORTALS": "Portals", "PORTALTOKEN": "Portal", @@ -12994,6 +13083,7 @@ "PROGE": "Protector Roge", "PROJECT89": "Project 89", "PROJECT89V1": "Project89", + "PROJECTARENA": "Arena", "PROJECTPAI": "Project Pai", "PROLIFIC": "Prolific Game Studio", "PROM": "Prometeus", @@ -13371,11 +13461,12 @@ "RAIF": "RAI Finance", "RAIIN": "Raiin", "RAIL": "Railgun", - "RAIN": "Rainmaker Games", + "RAIN": "Rain", "RAINBOW": "Rainbow Token", "RAINC": "RainCheck", "RAINCO": "Rain Coin", "RAINI": "Rainicorn", + "RAINMAKER": "Rainmaker Games", "RAIREFLEX": "Rai Reflex Index", "RAISE": "Raise Token", "RAIT": "Rabbitgame", @@ -13583,6 +13674,7 @@ "REP": "Augur", "REPE": "Resistance Pepe", "REPO": "Repo Coin", + "REPPO": "REPPO", "REPUB": "Republican", "REPUBLICAN": "Republican", "REPUX": "Repux", @@ -13603,6 +13695,7 @@ "RETH2": "rETH2", "RETIK": "Retik Finance", "RETIRE": "Retire Token", + "RETSA": "Retsa Coin", "REU": "REUCOIN", "REUNI": "Reunit Wallet", "REUSDC": "Relend USDC", @@ -13678,6 +13771,7 @@ "RIFA": "Rifampicin", "RIFI": "Rikkei Finance", "RIFT": "RIFT AI", + "RIFTS": "Rifts Finance", "RIGEL": "Rigel Finance", "RIK": "RIKEZA", "RIL": "Rilcoin", @@ -13733,6 +13827,7 @@ "RLM": "MarbleVerse", "RLOOP": "rLoop", "RLP": "Resolv RLP", + "RLS": "Rayls", "RLT": "Runner Land", "RLTM": "RealityToken", "RLUSD": "Ripple USD", @@ -14102,6 +14197,7 @@ "SATOEXCHANGE": "SatoExchange Token", "SATOPAY": "SatoPay", "SATORI": "Satori Network", + "SATOSHI": "SATOSHI•NAKAMOTO", "SATOSHINAKAMOTO": "Satoshi Nakamoto", "SATOTHEDOG": "Sato The Dog", "SATOX": "Satoxcoin", @@ -14263,6 +14359,7 @@ "SEEDS": "SeedShares", "SEEDV": "Seed Venture", "SEEDX": "SEEDx", + "SEEK": "Talisman", "SEELE": "Seele", "SEEN": "SEEN", "SEER": "SEER", @@ -14313,8 +14410,9 @@ "SERO": "Super Zero", "SERP": "Shibarium Perpetuals", "SERSH": "Serenity Shield", - "SERV": "Serve", + "SERV": "OpenServ", "SERVE": "Metavice", + "SERVEIO": "Serve", "SESE": "Simpson Pepe", "SESH": "Session Token", "SESSIA": "SESSIA", @@ -14673,6 +14771,7 @@ "SLAM": "Slam Token", "SLAP": "CatSlap", "SLAPS": "Slap", + "SLATE": "Slate", "SLAVI": "Slavi Coin", "SLAY": "SatLayer", "SLAYER": "ThreatSlayerAI by Virtuals", @@ -14719,7 +14818,7 @@ "SLUMBO": "SLUMBO", "SLVLUSD": "Staked Level USD", "SLVX": "eToro Silver", - "SLX": "Slate", + "SLX": "SLIMEX", "SMA": "Soma Network", "SMAC": "Social Media Coin", "SMAK": "Smartlink", @@ -15209,7 +15308,7 @@ "SSNC": "SatoshiSync", "SSOL": "Solayer SOL", "SSR": "SOL Strategic Reserve", - "SSS": "StarSharks", + "SSS": "Sparkle Token", "SSSSS": "Snake wif Hat", "SST": "SIMBA Storage Token", "SSTC": "SunShotCoin", @@ -15224,12 +15323,14 @@ "ST": "Skippy Token", "STA": "STOA Network", "STAB": "STABLE ASSET", + "STABLE": "Stable", "STABLZ": "Stablz", "STABUL": "Stabull Finance", "STAC": "STAC", "STACK": "StackOS", "STACKS": " STACKS PAY", "STACS": "STACS Token", + "STAFIRETH": "StaFi Staked ETH", "STAGE": "Stage", "STAK": "Jigstack", "STAKE": "xDai Chain", @@ -15253,6 +15354,7 @@ "STARRI": "starri", "STARS": "Stargaze", "STARSH": "StarShip Token", + "STARSHARKS": "StarSharks", "STARSHI": "Starship", "STARSHIP": "STARSHIP", "STARSHIPDOGE": "Starship Doge", @@ -15356,6 +15458,7 @@ "STOP": "LETSTOP", "STOR": "Self Storage Coin", "STORE": "Bit Store", + "STOREFUN": "FUN", "STOREP": "Storepay", "STORJ": "Storj", "STORM": "STORM", @@ -15753,6 +15856,7 @@ "TBILLV1": "OpenEden T-Bills v1", "TBIS": "TBIS token", "TBL": "Tombola", + "TBLLX": "TBLL xStock", "TBR": "Tuebor", "TBRIDGE": "tBridge Token", "TBT": "T-BOT", @@ -15760,6 +15864,7 @@ "TBTCV1": "tBTC v1", "TBULL": "Tron Bull", "TBX": "Tokenbox", + "TBY": "TOBY", "TCANDY": "TripCandy", "TCAP": "Total Crypto Market Cap", "TCAPY": "TonCapy", @@ -15781,6 +15886,7 @@ "TCR": "Tracer DAO", "TCS": "Timechain Swap Token", "TCT": "TokenClub", + "TCU29": "Tempestas Copper", "TCX": "T-Coin", "TCY": "The Crypto You", "TD": "The Big Red", @@ -15919,6 +16025,7 @@ "THEO": "Theopetra", "THEOS": "Theos", "THEP": "The Protocol", + "THEPLAY": "PLAY", "THERESAMAY": "Theresa May Coin", "THES": "The Standard Protocol (USDS)", "THESTANDARD": "Standard Token", @@ -16011,10 +16118,12 @@ "TITC": "TitCoin", "TITCOIN": "titcoin", "TITI": "TiTi Protocol", + "TITN": "Titan", "TITS": "We Love Tits", "TITTY": "TamaKitty", "TIUSD": "TiUSD", "TIX": "Blocktix", + "TJRM": "Tajir Tech Hub", "TKA": "Tokia", "TKAI": "TAIKAI", "TKB": "TokenBot", @@ -16056,6 +16165,7 @@ "TMT": "Tamy Token", "TMTG": "The Midas Touch Gold", "TMWH": "Tom Wif Hat", + "TMX": "TMX", "TN": "TurtleNetwork", "TNB": "Time New Bank", "TNC": "TNC Coin", @@ -16111,11 +16221,13 @@ "TONIC": "Tectonic", "TONK": "Tonk Inu", "TONNEL": "TONNEL Network", + "TONO": "Tonomy Token", "TONS": "TONSniper", "TONST": "Ton Stars", "TONT": "TONKIT", "TONTOKEN": "TONToken", "TONUP": "TonUP", + "TONXX": "TON xStock", "TONY": "TONY THE DUCK", "TOOB": "Toobcoin", "TOOBIGTORIG": "Too Big To Rig", @@ -16160,6 +16272,7 @@ "TOTM": "Totem", "TOTO": "TOTO", "TOTT": "TOTT", + "TOUCANPROTOCOL": "Toucan Protocol: Base Carbon Tonne", "TOUCHFAN": "TouchFan", "TOUCHG": "Touch Grass", "TOUR": "Tour Billion", @@ -16537,7 +16650,7 @@ "UCM": "UCROWDME", "UCN": "UCHAIN", "UCO": "Uniris", - "UCOIN": "Ucoin", + "UCOIN": "UCOIN", "UCON": "YouCoin Metaverse", "UCORE": "UnityCore Protocol", "UCR": "Ultra Clear", @@ -16579,6 +16692,7 @@ "UIS": "Unitus", "UJENNY": "Jenny Metaverse DAO Token", "UKG": "UnikoinGold", + "UKRAINEDAO": "UkraineDAO Flag NFT", "ULD": "Unlighted", "ULT": "Ultiledger", "ULTC": "Umbrella", @@ -16596,6 +16710,7 @@ "UMAMI": "Umami", "UMB": "Umbrella Network", "UMBR": "Umbria Network", + "UMBRA": "Umbra", "UMC": "Umbrella Coin", "UMI": "Universal Money Instrument", "UMID": "Umi Digital", @@ -16720,6 +16835,7 @@ "URS": "URUS", "URUS": "Urus Token", "URX": "URANIUMX", + "US": "Talus Token", "USA": "Based USA", "USACOIN": "American Coin", "USAT": "USAT", @@ -16731,6 +16847,7 @@ "USD0": "Usual", "USD1": "World Liberty Financial USD", "USD3": "Web 3 Dollar", + "USDA": "USDA", "USDACC": "USDA", "USDAI": "USDai", "USDAP": "Bond Appetite USD", @@ -16840,7 +16957,7 @@ "UTMDOGE": "UltramanDoge", "UTNP": "Universa", "UTON": "uTON", - "UTOPIA": "UCOIN", + "UTOPIA": "Utopia", "UTT": "uTrade", "UTU": "UTU Protocol", "UTX": "UTIX", @@ -16871,6 +16988,7 @@ "VAIOTV1": "VAIOT v1", "VAIX": "Vectorspace AI X", "VAL": "Validity", + "VALAN": "Valannium", "VALAS": "Valas Finance", "VALENTINE": "Valentine", "VALI": "VALIMARKET", @@ -16950,7 +17068,8 @@ "VEGASI": "Vegas Inu Token", "VEGASINO": "Vegasino", "VEGE": "Vege Token", - "VEIL": "VEIL", + "VEIL": "DarkVeil", + "VEILPROJECT": "VEIL", "VEKTOR": "VEKTOR", "VELA": "Vela Token", "VELAAI": "velaai", @@ -17001,6 +17120,7 @@ "VFIL": "Venus Filecoin", "VFOX": "VFOX", "VFT": "Value Finance", + "VFX": "ViFoxCoin", "VFY": "zkVerify", "VFYV1": "Verify Token", "VG": "Viu Ganhou", @@ -17354,6 +17474,7 @@ "WBONE": "Shibarium Wrapped BONE", "WBONES": "Wrapped BONES", "WBONK": "BONK (Portal Bridge)", + "WBRLY": "Wrapped BRLY", "WBS": "Websea", "WBT": "WhiteBIT Token", "WBTC": "Wrapped Bitcoin", @@ -17415,6 +17536,7 @@ "WEGLD": "Wrapped EGLD", "WEHMND": "Wrapped eHMND", "WEHODL": "HODL", + "WEIRD": "Weird Coin", "WEIRDO": "Weirdo", "WEL": "Welsh Corgi", "WELA": "Wrapped Elastos", @@ -17782,6 +17904,7 @@ "WWEMIX": "WWEMIX", "WWF": "WWF", "WWMATIC": "Wrapped Polygon (Wormhole)", + "WWROSE": "Wrapped Rose", "WWRY": "WeWillRugYou", "WWY": "WeWay", "WX": "WX Token", @@ -17982,6 +18105,7 @@ "XJEWEL": "xJEWEL", "XJO": "JouleCoin", "XKI": "Ki", + "XL1": "XL1", "XLA": "Scala", "XLAB": "Dexlab", "XLB": "LibertyCoin", @@ -18418,7 +18542,8 @@ "ZEBU": "ZEBU", "ZEC": "ZCash", "ZECD": "ZCashDarkCoin", - "ZED": "ZedCoins", + "ZED": "ZED Token", + "ZEDCOIN": "ZedCoin", "ZEDD": "ZedDex", "ZEDTOKEN": "Zed Token", "ZEDX": "ZEDX Сoin", @@ -18569,6 +18694,7 @@ "ZOOMER": "Zoomer Coin", "ZOON": "CryptoZoon", "ZOOT": "Zoo Token", + "ZOOTOPIA": "Zootopia", "ZORA": "Zora", "ZORACLES": "Zoracles", "ZORKSEES": "Zorksees", From bda4753468c44c280ef62714c443961b3ef14ad4 Mon Sep 17 00:00:00 2001 From: Eshaan Gupta <146680427+Eshaan-byte@users.noreply.github.com> Date: Wed, 17 Dec 2025 05:59:41 +1100 Subject: [PATCH 091/157] Task/remove deprecated Angular CLI decorator (#6071) * Remove deprecated Angular CLI decorator * Update changelog --- CHANGELOG.md | 1 + Dockerfile | 4 --- decorate-angular-cli.js | 79 ----------------------------------------- 3 files changed, 1 insertion(+), 83 deletions(-) delete mode 100644 decorate-angular-cli.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c0970b888..e5e803c65 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 +- Removed the deprecated _Angular CLI_ decorator (`decorate-angular-cli.js`) - Refreshed the cryptocurrencies list ## 2.223.0 - 2025-12-14 diff --git a/Dockerfile b/Dockerfile index 5beaf6e03..e73cd73e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,10 +22,6 @@ COPY ./prisma/schema.prisma prisma/ RUN npm install -# See https://github.com/nrwl/nx/issues/6586 for further details -COPY ./decorate-angular-cli.js decorate-angular-cli.js -RUN node decorate-angular-cli.js - COPY ./apps apps/ COPY ./libs libs/ COPY ./jest.config.ts jest.config.ts diff --git a/decorate-angular-cli.js b/decorate-angular-cli.js deleted file mode 100644 index d188492ea..000000000 --- a/decorate-angular-cli.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching - * and faster execution of tasks. - * - * It does this by: - * - * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. - * - Symlinking the ng to nx command, so all commands run through the Nx CLI - * - Updating the package.json postinstall script to give you control over this script - * - * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. - * Every command you run should work the same when using the Nx CLI, except faster. - * - * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, - * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked. - * The Nx CLI simply does some optimizations before invoking the Angular CLI. - * - * To opt out of this patch: - * - Replace occurrences of nx with ng in your package.json - * - Remove the script from your postinstall script in your package.json - * - Delete and reinstall your node_modules - */ - -const fs = require('fs'); -const os = require('os'); -const cp = require('child_process'); -const isWindows = os.platform() === 'win32'; -let output; -try { - output = require('@nx/workspace').output; -} catch (e) { - console.warn( - 'Angular CLI could not be decorated to enable computation caching. Please ensure @nx/workspace is installed.' - ); - process.exit(0); -} - -/** - * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still - * invoke the Nx CLI and get the benefits of computation caching. - */ -function symlinkNgCLItoNxCLI() { - try { - const ngPath = './node_modules/.bin/ng'; - const nxPath = './node_modules/.bin/nx'; - if (isWindows) { - /** - * This is the most reliable way to create symlink-like behavior on Windows. - * Such that it works in all shells and works with npx. - */ - ['', '.cmd', '.ps1'].forEach((ext) => { - if (fs.existsSync(nxPath + ext)) - fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); - }); - } else { - // If unix-based, symlink - cp.execSync(`ln -sf ./nx ${ngPath}`); - } - } catch (e) { - output.error({ - title: - 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + - e.message - }); - throw e; - } -} - -try { - symlinkNgCLItoNxCLI(); - require('@nrwl/cli/lib/decorate-cli').decorateCli(); - output.log({ - title: 'Angular CLI has been decorated to enable computation caching.' - }); -} catch (e) { - output.error({ - title: 'Decoration of the Angular CLI did not complete successfully' - }); -} From 1fa96536e09f7a5d3b30ee93bd5c1718b1baa7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Tue, 16 Dec 2025 21:09:17 +0100 Subject: [PATCH 092/157] Task/include first and last date of each calendar year in getChartDateMap() (#6069) * Include first and last date of each calendar year in getChartDateMap() * Update changelog --- CHANGELOG.md | 4 ++++ .../calculator/portfolio-calculator.ts | 22 +++++++++++++++++++ .../portfolio-calculator-baln-buy.spec.ts | 10 +++++++++ ...ulator-btceur-in-base-currency-eur.spec.ts | 11 ++++++++++ .../roai/portfolio-calculator-btceur.spec.ts | 11 ++++++++++ .../roai/portfolio-calculator-btcusd.spec.ts | 11 ++++++++++ 6 files changed, 69 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e803c65..5097993c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Included the calendar year boundaries in the portfolio calculations + ### Changed - Removed the deprecated _Angular CLI_ decorator (`decorate-angular-cli.js`) diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index b3cedb00b..ee4219b58 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -44,11 +44,15 @@ import { plainToClass } from 'class-transformer'; import { differenceInDays, eachDayOfInterval, + eachYearOfInterval, endOfDay, + endOfYear, format, isAfter, isBefore, + isWithinInterval, min, + startOfYear, subDays } from 'date-fns'; import { isNumber, sortBy, sum, uniqBy } from 'lodash'; @@ -889,6 +893,24 @@ export abstract class PortfolioCalculator { } } + // Make sure the first and last date of each calendar year is present + const interval = { start: startDate, end: endDate }; + + for (const date of eachYearOfInterval(interval)) { + const yearStart = startOfYear(date); + const yearEnd = endOfYear(date); + + if (isWithinInterval(yearStart, interval)) { + // Add start of year (YYYY-01-01) + chartDateMap[format(yearStart, DATE_FORMAT)] = true; + } + + if (isWithinInterval(yearEnd, interval)) { + // Add end of year (YYYY-12-31) + chartDateMap[format(yearEnd, DATE_FORMAT)] = true; + } + } + return chartDateMap; } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index 84cab99e1..bfa4d06f3 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -109,6 +109,12 @@ describe('PortfolioCalculator', () => { const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + const historicalDataDates = portfolioSnapshot.historicalData.map( + ({ date }) => { + return date; + } + ); + const investments = portfolioCalculator.getInvestments(); const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ @@ -170,8 +176,12 @@ describe('PortfolioCalculator', () => { totalLiabilitiesWithCurrencyEffect: new Big('0') }); + expect(historicalDataDates).not.toContain('2021-01-01'); + expect(historicalDataDates).not.toContain('2021-12-31'); + expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( expect.objectContaining({ + date: '2021-12-18', netPerformance: 23.05, netPerformanceInPercentage: 0.08437042459736457, netPerformanceInPercentageWithCurrencyEffect: 0.08437042459736457, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts index 1f64684a0..84ea6c251 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts @@ -130,6 +130,17 @@ describe('PortfolioCalculator', () => { const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + const historicalDataDates = portfolioSnapshot.historicalData.map( + ({ date }) => { + return date; + } + ); + + expect(historicalDataDates).not.toContain('2021-01-01'); + expect(historicalDataDates).toContain('2021-12-31'); + expect(historicalDataDates).toContain('2022-01-01'); + expect(historicalDataDates).not.toContain('2022-12-31'); + expect(portfolioSnapshot.positions[0].fee).toEqual(new Big(4.46)); expect( portfolioSnapshot.positions[0].feeInBaseCurrency.toNumber() diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index ce639b564..32b3f05c2 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -118,6 +118,12 @@ describe('PortfolioCalculator', () => { const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + const historicalDataDates = portfolioSnapshot.historicalData.map( + ({ date }) => { + return date; + } + ); + const investments = portfolioCalculator.getInvestments(); const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ @@ -225,6 +231,11 @@ describe('PortfolioCalculator', () => { totalLiabilitiesWithCurrencyEffect: new Big('0') }); + expect(historicalDataDates).not.toContain('2021-01-01'); + expect(historicalDataDates).toContain('2021-12-31'); + expect(historicalDataDates).toContain('2022-01-01'); + expect(historicalDataDates).not.toContain('2022-12-31'); + expect(investments).toEqual([ { date: '2021-12-12', investment: new Big('44558.42') } ]); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index a7cbe746c..716ec7a59 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -118,6 +118,12 @@ describe('PortfolioCalculator', () => { const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + const historicalDataDates = portfolioSnapshot.historicalData.map( + ({ date }) => { + return date; + } + ); + const investments = portfolioCalculator.getInvestments(); const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ @@ -225,6 +231,11 @@ describe('PortfolioCalculator', () => { totalLiabilitiesWithCurrencyEffect: new Big('0') }); + expect(historicalDataDates).not.toContain('2021-01-01'); + expect(historicalDataDates).toContain('2021-12-31'); + expect(historicalDataDates).toContain('2022-01-01'); + expect(historicalDataDates).not.toContain('2022-12-31'); + expect(investments).toEqual([ { date: '2021-12-12', investment: new Big('44558.42') } ]); From 48952c9bbd39a65948c88d1c6de47482ebf3f8b9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:24:05 +0100 Subject: [PATCH 093/157] Task/improve localization of various components (#6074) * Improve localization --- .../admin-platform/admin-platform.component.html | 2 +- .../admin-tag/admin-tag.component.html | 2 +- .../app/components/admin-users/admin-users.html | 3 +-- .../portfolio-summary.component.html | 4 ++-- .../pages/portfolio/analysis/analysis-page.html | 16 ++++++++-------- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.html b/apps/client/src/app/components/admin-platform/admin-platform.component.html index a5a1430d4..367827878 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.html +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -57,7 +57,7 @@ - + - + @if (hasPermissionToImpersonateAllUsers) { - - - - - - } @if (assetProfile?.dataSource === 'MANUAL') {
@@ -588,6 +464,115 @@
+ @if (assetProfile?.dataSource === 'MANUAL') { + + + +
Scraper Configuration
+
+
+
+
+
+ + Default Market Price + + +
+
+ + HTTP Request Headers + + +
+
+ + Locale + + +
+
+ + Mode + + @for (modeValue of modeValues; track modeValue) { + {{ + modeValue.viewValue + }} + } + + +
+
+ + + Selector* + + + +
+
+ + + Url* + + + +
+
+ +
+
+
+
+
+ } From 95cbd01a4c5becac550c69a69f74fa35fcf5502d Mon Sep 17 00:00:00 2001 From: Omkar Gujja <67428719+omkarg01@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:09:18 +0530 Subject: [PATCH 134/157] Task/lazy load platforms via API in create or update account dialog (#6130) * Lazy load platforms via API * Update changelog --- CHANGELOG.md | 1 + ...eate-or-update-account-dialog.component.ts | 44 +++++++++++-------- apps/client/src/app/services/data.service.ts | 5 +++ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cabbc605..91fc2bc39 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 +- Integrated the endpoint to get all platforms (`GET api/v1/platforms`) into the create or update account dialog - Extracted the scraper configuration to a dedicated tab in the asset profile details dialog of the admin control panel ## 2.227.0 - 2026-01-02 diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index 5e18f25cf..ceb11a011 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -59,7 +59,7 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { public accountForm: FormGroup; public currencies: string[] = []; public filteredPlatforms: Observable; - public platforms: Platform[]; + public platforms: Platform[] = []; private unsubscribeSubject = new Subject(); @@ -71,10 +71,8 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { ) {} public ngOnInit() { - const { currencies, platforms } = this.dataService.fetchInfo(); - + const { currencies } = this.dataService.fetchInfo(); this.currencies = currencies; - this.platforms = platforms; this.accountForm = this.formBuilder.group({ accountId: [{ disabled: true, value: this.data.account.id }], @@ -83,23 +81,33 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { currency: [this.data.account.currency, Validators.required], isExcluded: [this.data.account.isExcluded], name: [this.data.account.name, Validators.required], - platformId: [ - this.platforms.find(({ id }) => { - return id === this.data.account.platformId; - }), - this.autocompleteObjectValidator() - ] + platformId: [null, this.autocompleteObjectValidator()] }); - this.filteredPlatforms = this.accountForm - .get('platformId') - .valueChanges.pipe( - startWith(''), - map((value) => { - const name = typeof value === 'string' ? value : value?.name; - return name ? this.filter(name as string) : this.platforms.slice(); - }) + this.dataService.fetchPlatforms().subscribe(({ platforms }) => { + this.platforms = platforms; + + const selectedPlatform = this.platforms.find(({ id }) => { + return id === this.data.account.platformId; + }); + + this.accountForm.patchValue( + { + platformId: selectedPlatform + }, + { emitEvent: false } ); + + this.filteredPlatforms = this.accountForm + .get('platformId') + .valueChanges.pipe( + startWith(''), + map((value) => { + const name = typeof value === 'string' ? value : value?.name; + return name ? this.filter(name as string) : this.platforms.slice(); + }) + ); + }); } public autoCompleteCheck() { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 31b0fef73..21eec06c3 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -42,6 +42,7 @@ import { MarketDataDetailsResponse, MarketDataOfMarketsResponse, OAuthResponse, + PlatformsResponse, PortfolioDetails, PortfolioDividendsResponse, PortfolioHoldingResponse, @@ -521,6 +522,10 @@ export class DataService { ); } + public fetchPlatforms() { + return this.http.get('/api/v1/platforms'); + } + public fetchPortfolioDetails({ filters, withMarkets = false From 113238f86ee73491ea961672c7c9f4885463ecab Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Jan 2026 08:48:20 +0100 Subject: [PATCH 135/157] Task/remove deprecated public Stripe key (part 2) (#6138) * Remove deprecated public Stripe key --- libs/common/src/lib/interfaces/info-item.interface.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index c2ee54526..119a94a7c 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -18,9 +18,5 @@ export interface InfoItem { platforms: Platform[]; statistics: Statistics; - - /** @deprecated */ - stripePublicKey?: string; - subscriptionOffer?: SubscriptionOffer; } From 61d51ec2074703176c649546aacba039e4375577 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 11:03:57 +0100 Subject: [PATCH 136/157] Feature/update locales (#6083) * Update locales * Update translations * Update changelog --------- Co-authored-by: github-actions[bot] Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/client/src/locales/messages.ca.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.de.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.es.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.fr.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.it.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.nl.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.pl.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.pt.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.tr.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.uk.xlf | 198 +++++++++++++++--------- apps/client/src/locales/messages.xlf | 194 ++++++++++++++--------- apps/client/src/locales/messages.zh.xlf | 198 +++++++++++++++--------- 13 files changed, 1449 insertions(+), 924 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91fc2bc39..d5b26a07a 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 - Integrated the endpoint to get all platforms (`GET api/v1/platforms`) into the create or update account dialog - Extracted the scraper configuration to a dedicated tab in the asset profile details dialog of the admin control panel +- Improved the language localization for German (`de`) ## 2.227.0 - 2026-01-02 diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 1340b5da7..762f8f73a 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -371,7 +371,7 @@ Realment vol revocar aquest accés? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113
@@ -435,7 +435,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -503,11 +503,11 @@ Divisa apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -619,7 +619,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -695,7 +695,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -711,7 +711,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -919,7 +919,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1023,7 +1023,7 @@ El preu de mercat actual és apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -1055,7 +1055,7 @@ Sector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1067,7 +1067,7 @@ País apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -1087,11 +1087,11 @@ Sectors apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1107,11 +1107,11 @@ Països apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1123,7 +1123,7 @@ Mapatge de Símbols apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -1139,7 +1139,7 @@ Configuració del Proveïdor de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -1147,7 +1147,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -1155,11 +1155,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1170,12 +1170,20 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Note Notes apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1215,7 +1223,7 @@ Nom, símbol o ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1323,7 +1331,7 @@ Recollida de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1419,7 +1427,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -1491,7 +1499,7 @@ Està segur que vol eliminar aquest usuari? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -1554,6 +1562,18 @@ 254 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Comparar amb... @@ -1599,7 +1619,7 @@ Punt de Referència apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1935,7 +1955,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -2183,7 +2203,7 @@ Definiu l’import del vostre fons d’emergència. apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -2343,7 +2363,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -2355,7 +2375,7 @@ 1 any apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2367,7 +2387,7 @@ 5 anys apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2379,7 +2399,7 @@ Màx apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2455,7 +2475,7 @@ Introduïu el vostre codi de cupó. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -2463,7 +2483,7 @@ No s’ha pogut bescanviar el codi de cupó apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -2471,7 +2491,7 @@ El codi del cupó s’ha bescanviat apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -2479,7 +2499,7 @@ Torna a carregar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -2523,7 +2543,7 @@ Automàtic apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2551,7 +2571,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -2607,7 +2627,7 @@ Localització apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2779,7 +2799,7 @@ D’acord apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -3063,7 +3083,7 @@ Visió general apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -3202,6 +3222,18 @@ 225 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Descobriu les últimes actualitzacions i perspectives de Ghostfolio sobre finances personals @@ -3464,7 +3496,7 @@ Mercats apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -3976,7 +4008,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -4819,6 +4851,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. És gratuït. @@ -5393,7 +5437,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5413,7 +5457,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5433,7 +5477,7 @@ any apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5453,7 +5497,7 @@ anys apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5701,11 +5745,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5733,11 +5777,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5913,7 +5957,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6613,7 +6657,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6661,11 +6705,11 @@ Cancel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6717,7 +6761,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7175,7 +7219,7 @@ Could not generate an API key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7183,7 +7227,7 @@ Set this API key in your self-hosted environment: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7191,7 +7235,7 @@ Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7199,7 +7243,7 @@ Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7239,7 +7283,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7259,7 +7303,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7331,7 +7375,7 @@ Link has been copied to the clipboard apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7339,7 +7383,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Apply apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 4b405ce30..9a429cfad 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -102,7 +102,7 @@ Möchtest du diese Zugangsberechtigung wirklich widerrufen? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -114,7 +114,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -266,7 +266,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -326,7 +326,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -402,7 +402,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -482,7 +482,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -654,7 +654,7 @@ Möchtest du diesen Benutzer wirklich löschen? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -978,7 +978,7 @@ Bitte setze den Betrag deines Notfallfonds. apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -986,11 +986,11 @@ Sektoren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1006,11 +1006,11 @@ Länder apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1086,7 +1086,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1098,7 +1098,7 @@ 1J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1110,7 +1110,7 @@ 5J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1122,7 +1122,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1134,7 +1134,7 @@ Okay apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -1202,7 +1202,7 @@ Bitte gebe deinen Gutscheincode ein. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -1210,7 +1210,7 @@ Gutscheincode konnte nicht eingelöst werden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -1218,7 +1218,7 @@ Gutscheincode wurde eingelöst apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -1226,7 +1226,7 @@ Neu laden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -1294,7 +1294,7 @@ Lokalität apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1430,11 +1430,11 @@ Währung apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1674,7 +1674,7 @@ Übersicht apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -1710,7 +1710,7 @@ Märkte apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -1930,7 +1930,7 @@ Aktuelle Woche apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1962,7 +1962,7 @@ Name, Symbol oder ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2014,7 +2014,7 @@ Kommentar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2038,7 +2038,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -2430,7 +2430,7 @@ Sektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2442,7 +2442,7 @@ Land apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -2657,6 +2657,18 @@ 235 + + Could not validate form + Das Formular konnte nicht validiert werden + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Vergleichen mit... @@ -2670,7 +2682,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2698,7 +2710,7 @@ Automatisch apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2782,11 +2794,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2818,7 +2830,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -3110,7 +3122,7 @@ Symbol Zuordnung apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -3170,11 +3182,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3601,6 +3613,18 @@ 288 + + Could not save asset profile + Das Anlageprofil konnte nicht gespeichert werden + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. Es ist kostenlos. @@ -3822,7 +3846,7 @@ Aktuelles Jahr apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -3838,11 +3862,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3853,6 +3877,14 @@ 25 + + Asset profile has been saved + Das Anlageprofil wurde gespeichert + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Möchtest du diese Plattform wirklich löschen? @@ -4210,7 +4242,7 @@ Scraper Konfiguration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -4753,6 +4785,18 @@ 7 + + Could not parse scraper configuration + Die Scraper Konfiguration konnte nicht geparsed werden + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Entdecke die neuesten Ghostfolio Updates und Beiträge zu persönlichen Finanzen @@ -5736,7 +5780,7 @@ Der aktuelle Marktpreis ist apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5744,7 +5788,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5904,7 +5948,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5924,7 +5968,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5972,7 +6016,7 @@ Jahr apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5992,7 +6036,7 @@ Jahre apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -6012,7 +6056,7 @@ Finanzmarktdaten synchronisieren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6237,7 +6281,7 @@ Berücksichtigen in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6637,7 +6681,7 @@ Fehler apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6685,11 +6729,11 @@ Abbrechen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6741,7 +6785,7 @@ Schliessen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7199,7 +7243,7 @@ API-Schlüssel konnte nicht erstellt werden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7207,7 +7251,7 @@ Setze diesen API-Schlüssel in deiner selbst gehosteten Umgebung: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7215,7 +7259,7 @@ API-Schlüssel für den Ghostfolio Premium Datenanbieter apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7223,7 +7267,7 @@ Möchtest du wirklich einen neuen API-Schlüssel erstellen? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7263,7 +7307,7 @@ Speichern apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7283,7 +7327,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7355,7 +7399,7 @@ Link wurde in die Zwischenablage kopiert apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7363,7 +7407,7 @@ Verzögert apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7371,7 +7415,7 @@ Sofort apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7379,7 +7423,7 @@ Standardmarktpreis apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7387,7 +7431,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7395,7 +7439,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7403,7 +7447,7 @@ HTTP Request-Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7411,7 +7455,7 @@ Tagesende apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7419,7 +7463,7 @@ in Echtzeit apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7567,7 +7611,7 @@ Sicherheits-Token apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7579,7 +7623,7 @@ Möchtest du für diesen Benutzer wirklich ein neues Sicherheits-Token generieren? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7652,7 +7696,7 @@ () wird bereits verwendet. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7660,7 +7704,7 @@ Bei der Änderung zu () ist ein Fehler aufgetreten. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7668,7 +7712,7 @@ Übernehmen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Aktueller Monat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 6e5bf1dba..9be8108bd 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -103,7 +103,7 @@ ¿Quieres revocar el acceso concedido? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -115,7 +115,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -267,7 +267,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -327,7 +327,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -403,7 +403,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -483,7 +483,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -639,7 +639,7 @@ ¿Estás seguro de eliminar este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -963,7 +963,7 @@ Por favor, ingresa la cantidad de tu fondo de emergencia: apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -971,11 +971,11 @@ Sectores apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -991,11 +991,11 @@ Países apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1071,7 +1071,7 @@ Desde principio de año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1083,7 +1083,7 @@ 1 año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1095,7 +1095,7 @@ 5 años apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1107,7 +1107,7 @@ Máximo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1119,7 +1119,7 @@ De acuerdo apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -1187,7 +1187,7 @@ Por favor, ingresa tu código de cupón: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -1195,7 +1195,7 @@ No se puede canjear este código de cupón apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -1203,7 +1203,7 @@ El codigo de cupón ha sido canjeado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -1211,7 +1211,7 @@ Refrescar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -1279,7 +1279,7 @@ Ubicación apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1415,11 +1415,11 @@ Divisa base apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1659,7 +1659,7 @@ Visión general apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -1695,7 +1695,7 @@ Mercados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -1915,7 +1915,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1947,7 +1947,7 @@ Nombre, símbolo o ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1999,7 +1999,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2023,7 +2023,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -2463,7 +2463,7 @@ Sector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2475,7 +2475,7 @@ País apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -2647,13 +2647,25 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts 152 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Comparar con... @@ -2683,7 +2695,7 @@ Automático apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2767,11 +2779,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2803,7 +2815,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -3095,7 +3107,7 @@ Mapeo de símbolos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -3155,11 +3167,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3586,6 +3598,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. Es gratis. @@ -3799,7 +3823,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -3815,11 +3839,11 @@ ¿La URL? apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3830,6 +3854,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? ¿Realmente deseas eliminar esta plataforma? @@ -4187,7 +4219,7 @@ Configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -4730,6 +4762,18 @@ 7 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Conoce las últimas actualizaciones de Ghostfolio y obtén información sobre finanzas personales @@ -5713,7 +5757,7 @@ El precio actual de mercado es apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5721,7 +5765,7 @@ Prueba apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5881,7 +5925,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5901,7 +5945,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5949,7 +5993,7 @@ año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5969,7 +6013,7 @@ años apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5989,7 +6033,7 @@ Recopilación de datos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6214,7 +6258,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6614,7 +6658,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6662,11 +6706,11 @@ Cancelar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6718,7 +6762,7 @@ Cerca apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7176,7 +7220,7 @@ No se pudo generar una clave API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7184,7 +7228,7 @@ Configure esta clave API en su entorno autohospedado: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7192,7 +7236,7 @@ Clave API del proveedor de datos premium de Ghostfolio apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7200,7 +7244,7 @@ ¿Realmente desea generar una nueva clave API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7240,7 +7284,7 @@ Ahorrar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7260,7 +7304,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7332,7 +7376,7 @@ El enlace ha sido copiado al portapapeles apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7340,7 +7384,7 @@ Perezoso apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7348,7 +7392,7 @@ Instantáneo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7356,7 +7400,7 @@ Precio de mercado por defecto apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7364,7 +7408,7 @@ Modo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7372,7 +7416,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7380,7 +7424,7 @@ Encabezados de solicitud HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7388,7 +7432,7 @@ final del día apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7396,7 +7440,7 @@ en tiempo real apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7544,7 +7588,7 @@ Token de seguridad apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7556,7 +7600,7 @@ ¿Realmente deseas generar un nuevo token de seguridad para este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7629,7 +7673,7 @@ () ya está en uso. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7637,7 +7681,7 @@ Ocurrió un error al actualizar a (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7645,7 +7689,7 @@ Aplicar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7988,7 +8032,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 65c3e54f5..5bb9d6489 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -94,7 +94,7 @@ Voulez-vous vraiment révoquer cet accès ? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -122,7 +122,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -190,11 +190,11 @@ Devise apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -322,7 +322,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -374,7 +374,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -458,7 +458,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -546,7 +546,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -618,7 +618,7 @@ Secteur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -630,7 +630,7 @@ Pays apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -650,11 +650,11 @@ Secteurs apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -670,11 +670,11 @@ Pays apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -686,7 +686,7 @@ Équivalence de Symboles apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -694,7 +694,7 @@ Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -850,7 +850,7 @@ Voulez-vous vraiment supprimer cet·te utilisateur·rice ? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -897,6 +897,18 @@ 186 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Comparer avec... @@ -934,7 +946,7 @@ Référence apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1242,7 +1254,7 @@ Veuillez entrer le montant de votre fonds d’urgence : apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -1306,7 +1318,7 @@ CDA apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1318,7 +1330,7 @@ 1A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1330,7 +1342,7 @@ 5A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1342,7 +1354,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1390,7 +1402,7 @@ D’accord apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -1458,7 +1470,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1470,7 +1482,7 @@ Veuillez entrer votre code promotionnel. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -1478,7 +1490,7 @@ Le code promotionnel n’a pas pu être appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -1486,7 +1498,7 @@ Le code promotionnel a été appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -1494,7 +1506,7 @@ Rafraîchir apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -1570,7 +1582,7 @@ Paramètres régionaux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1978,7 +1990,7 @@ Marchés apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -2034,7 +2046,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -2082,7 +2094,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -2114,7 +2126,7 @@ Nom, symbole, ou ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2686,7 +2698,7 @@ Aperçu apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -2914,11 +2926,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2946,11 +2958,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3006,7 +3018,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -3585,6 +3597,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. C’est gratuit. @@ -3798,7 +3822,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -3814,11 +3838,11 @@ Lien apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3829,6 +3853,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Voulez-vous vraiment supprimer cette plateforme ? @@ -4186,7 +4218,7 @@ Configuration du Scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -4729,6 +4761,18 @@ 7 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Découvrez les dernières mises à jour et informations de Ghostfolio sur la gestion de patrimoine @@ -5712,7 +5756,7 @@ Le prix actuel du marché est apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5720,7 +5764,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5880,7 +5924,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5900,7 +5944,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5948,7 +5992,7 @@ année apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5968,7 +6012,7 @@ années apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5988,7 +6032,7 @@ Collecter les données apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6213,7 +6257,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6613,7 +6657,7 @@ Erreur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6661,11 +6705,11 @@ Annuler apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6717,7 +6761,7 @@ Fermer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7175,7 +7219,7 @@ Impossible de générer une clé API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7183,7 +7227,7 @@ Définissez cette clé API dans votre environnement auto-hébergé : apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7191,7 +7235,7 @@ Clé API du fournisseur de données Ghostfolio Premium apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7199,7 +7243,7 @@ Voulez-vous vraiment générer une nouvelle clé API ? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7239,7 +7283,7 @@ Sauvegarder apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7259,7 +7303,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7331,7 +7375,7 @@ Le lien a été copié dans le presse-papiers apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7339,7 +7383,7 @@ Paresseux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Instantané apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Prix du marché par défaut apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Selecteur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ En-têtes de requête HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ fin de journée apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ temps réel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Jeton de sécurité apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Voulez-vous vraiment générer un nouveau jeton de sécurité pour cet utilisateur ? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () est déjà utilisé. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ Une erreur s’est produite lors de la mise à jour vers (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Appliquer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index f92049f9c..2986e8ee4 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -103,7 +103,7 @@ Vuoi davvero revocare l’accesso concesso? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -115,7 +115,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -267,7 +267,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -327,7 +327,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -403,7 +403,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -483,7 +483,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -639,7 +639,7 @@ Vuoi davvero eliminare questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -963,7 +963,7 @@ Inserisci l’importo del tuo fondo di emergenza: apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -971,11 +971,11 @@ Settori apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -991,11 +991,11 @@ Paesi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1071,7 +1071,7 @@ anno corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1083,7 +1083,7 @@ 1 anno apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1095,7 +1095,7 @@ 5 anni apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1107,7 +1107,7 @@ Massimo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1119,7 +1119,7 @@ Bene apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -1187,7 +1187,7 @@ Inserisci il tuo codice del buono: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -1195,7 +1195,7 @@ Impossibile riscattare il codice del buono apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -1203,7 +1203,7 @@ Il codice del buono è stato riscattato apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -1211,7 +1211,7 @@ Ricarica apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -1279,7 +1279,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1415,11 +1415,11 @@ Valuta apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1659,7 +1659,7 @@ Panoramica apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -1695,7 +1695,7 @@ Mercati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -1915,7 +1915,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1947,7 +1947,7 @@ Nome, simbolo o ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1999,7 +1999,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2023,7 +2023,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -2463,7 +2463,7 @@ Settore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2475,7 +2475,7 @@ Paese apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -2647,13 +2647,25 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts 152 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Confronta con... @@ -2683,7 +2695,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2767,11 +2779,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2803,7 +2815,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -3095,7 +3107,7 @@ Mappatura dei simboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -3155,11 +3167,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3586,6 +3598,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. È gratuito. @@ -3799,7 +3823,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -3815,11 +3839,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3830,6 +3854,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Vuoi davvero eliminare questa piattaforma? @@ -4187,7 +4219,7 @@ Configurazione dello scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -4730,6 +4762,18 @@ 7 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Scopri gli ultimi aggiornamenti e approfondimenti di Ghostfolio sulla finanza personale @@ -5713,7 +5757,7 @@ L’attuale prezzo di mercato è apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5721,7 +5765,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5881,7 +5925,7 @@ Settimana corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5901,7 +5945,7 @@ Mese corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5949,7 +5993,7 @@ anno apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5969,7 +6013,7 @@ anni apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5989,7 +6033,7 @@ Raccolta Dati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6214,7 +6258,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6614,7 +6658,7 @@ Errore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6662,11 +6706,11 @@ Annulla apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6718,7 +6762,7 @@ Chiudi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7176,7 +7220,7 @@ Non è stato possibile generare un API key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7184,7 +7228,7 @@ Imposta questa API key nel tuo ambiente self-hosted: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7192,7 +7236,7 @@ API Key for Ghostfolio Premium Data Provider apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7200,7 +7244,7 @@ Vuoi davvero generare una nuova API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7240,7 +7284,7 @@ Salva apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7260,7 +7304,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7332,7 +7376,7 @@ Il link è stato copiato negli appunti apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7340,7 +7384,7 @@ Pigro apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7348,7 +7392,7 @@ Istantaneo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7356,7 +7400,7 @@ Prezzo di mercato predefinito apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7364,7 +7408,7 @@ Modalità apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7372,7 +7416,7 @@ Selettore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7380,7 +7424,7 @@ Intestazioni della richiesta HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7388,7 +7432,7 @@ fine giornata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7396,7 +7440,7 @@ in tempo reale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7544,7 +7588,7 @@ Token di sicurezza apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7556,7 +7600,7 @@ Vuoi davvero generare un nuovo token di sicurezza per questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7629,7 +7673,7 @@ () e gia in uso. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7637,7 +7681,7 @@ Si è verificato un errore durante l’aggiornamento di (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7645,7 +7689,7 @@ Applica apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7988,7 +8032,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index b3b9d8040..c34f259bc 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -102,7 +102,7 @@ Wil je deze verleende toegang echt intrekken? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -114,7 +114,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -266,7 +266,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -326,7 +326,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -402,7 +402,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -482,7 +482,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -638,7 +638,7 @@ Wilt je deze gebruiker echt verwijderen? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -962,7 +962,7 @@ Voer het bedrag van je noodfonds in: apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -970,11 +970,11 @@ Sectoren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -990,11 +990,11 @@ Landen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1070,7 +1070,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1082,7 +1082,7 @@ 1J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1094,7 +1094,7 @@ 5J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1106,7 +1106,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1118,7 +1118,7 @@ Oké apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -1186,7 +1186,7 @@ Voer je couponcode in: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -1194,7 +1194,7 @@ Kon je kortingscode niet inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -1202,7 +1202,7 @@ Je couponcode is ingewisseld apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -1210,7 +1210,7 @@ Herladen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -1278,7 +1278,7 @@ Locatie apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1414,11 +1414,11 @@ Valuta apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1658,7 +1658,7 @@ Overzicht apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -1694,7 +1694,7 @@ Markten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -1914,7 +1914,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1946,7 +1946,7 @@ Naam, symbool of ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1998,7 +1998,7 @@ Opmerking apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2022,7 +2022,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -2462,7 +2462,7 @@ Sector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2474,7 +2474,7 @@ Land apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -2646,13 +2646,25 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts 152 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Vergelijk met... @@ -2682,7 +2694,7 @@ Automatisch apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2766,11 +2778,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2802,7 +2814,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -3094,7 +3106,7 @@ Symbool toewijzen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -3154,11 +3166,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3585,6 +3597,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. Het is gratis. @@ -3798,7 +3822,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -3814,11 +3838,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3829,6 +3853,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Wil je dit platform echt verwijderen? @@ -4186,7 +4218,7 @@ Scraper instellingen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -4729,6 +4761,18 @@ 7 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Ontdek de nieuwste Ghostfolio-updates en inzichten in persoonlijke financiën @@ -5712,7 +5756,7 @@ De huidige markt waarde is apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5720,7 +5764,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5880,7 +5924,7 @@ Week tot nu toe apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5900,7 +5944,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5948,7 +5992,7 @@ jaar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5968,7 +6012,7 @@ jaren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5988,7 +6032,7 @@ Data Verzamelen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6213,7 +6257,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6613,7 +6657,7 @@ Fout apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6661,11 +6705,11 @@ Annuleren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6717,7 +6761,7 @@ Sluiten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7175,7 +7219,7 @@ Er kon geen API-sleutel worden gegenereerd apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7183,7 +7227,7 @@ Stel deze API-sleutel in uw zelf-gehoste omgeving in: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7191,7 +7235,7 @@ Ghostfolio Premium Gegevensleverancier API-sleutel apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7199,7 +7243,7 @@ Wilt u echt een nieuwe API-sleutel genereren? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7239,7 +7283,7 @@ Opslaan apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7259,7 +7303,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7331,7 +7375,7 @@ Link is gekopieerd naar klemboord apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7339,7 +7383,7 @@ Lui apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Direct apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Standaard Marktprijs apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Kiezer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ HTTP Verzoek Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ eind van de dag apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Beveiligingstoken apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Wilt u echt een nieuw beveiligingstoken voor deze gebruiker aanmaken? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () is al in gebruik. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ Er is een fout opgetreden tijdens het updaten naar (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Toepassen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 28c63f231..2241e6a9a 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -303,7 +303,7 @@ Czy na pewno chcesz cofnąć przyznany dostęp? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -359,7 +359,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -427,11 +427,11 @@ Waluta apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -543,7 +543,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -599,7 +599,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -615,7 +615,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -807,7 +807,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -903,7 +903,7 @@ Sektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -915,7 +915,7 @@ Kraj apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -935,11 +935,11 @@ Sektory apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -955,11 +955,11 @@ Kraje apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -971,7 +971,7 @@ Mapowanie Symboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -987,7 +987,7 @@ Konfiguracja Scrapera apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -995,7 +995,7 @@ Notatka apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1035,7 +1035,7 @@ Nazwa, symbol lub ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1203,11 +1203,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1218,6 +1218,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Czy naprawdę chcesz usunąć tę platformę? @@ -1247,7 +1255,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -1319,7 +1327,7 @@ Czy na pewno chcesz usunąć tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -1382,6 +1390,18 @@ 254 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Porównaj z... @@ -1427,7 +1447,7 @@ Poziom Odniesienia (Benchmark) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1631,7 +1651,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1867,7 +1887,7 @@ Wprowadź wysokość funduszu rezerwowego: apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -2099,7 +2119,7 @@ Liczony od początku roku (year-to-date) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -2111,7 +2131,7 @@ 1 rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2123,7 +2143,7 @@ 5 lat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2135,7 +2155,7 @@ Maksimum apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2171,7 +2191,7 @@ Wpisz kod kuponu: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -2179,7 +2199,7 @@ Nie udało się zrealizować kodu kuponu apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -2187,7 +2207,7 @@ Kupon został zrealizowany apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -2195,7 +2215,7 @@ Odśwież apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -2239,7 +2259,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2291,7 +2311,7 @@ Ustawienia Regionalne apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2455,7 +2475,7 @@ Okej apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -2723,7 +2743,7 @@ Przegląd apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -2862,6 +2882,18 @@ 225 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Odkryj najnowsze aktualizacje Ghostfolio oraz spostrzeżenia na temat finansów osobistych @@ -3099,7 +3131,7 @@ Rynki apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -3595,7 +3627,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -4390,6 +4422,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. Jest bezpłatny. @@ -5092,11 +5136,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5124,11 +5168,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5296,7 +5340,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -5712,7 +5756,7 @@ Obecna cena rynkowa wynosi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5720,7 +5764,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5880,7 +5924,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5900,7 +5944,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5948,7 +5992,7 @@ rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5968,7 +6012,7 @@ lata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5988,7 +6032,7 @@ Gromadzenie Danych apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6213,7 +6257,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6613,7 +6657,7 @@ Błąd apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6661,11 +6705,11 @@ Anuluj apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6717,7 +6761,7 @@ Zamknij apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7175,7 +7219,7 @@ Nie udało się wygenerować klucza API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7183,7 +7227,7 @@ Ustaw ten klucz API w samodzielnie hostowanym środowisku: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7191,7 +7235,7 @@ Klucz API dostawcy danych Premium Ghostfolio apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7199,7 +7243,7 @@ Czy na pewno chcesz wygenerować nowy klucz API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7239,7 +7283,7 @@ Zapisz apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7259,7 +7303,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7331,7 +7375,7 @@ Link został skopiowany do schowka apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7339,7 +7383,7 @@ Leniwy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Natychmiastowy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Domyślna cena rynkowa apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Tryb apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ Nagłówki żądań HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ koniec dnia apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ w czasie rzeczywistym apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Token bezpieczeństwa apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Czy napewno chcesz wygenerować nowy token bezpieczeństwa dla tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () jest już w użyciu. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ Wystąpił błąd podczas aktualizacji do (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Zatwierdź apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 812d24b20..188bda9c8 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -94,7 +94,7 @@ Pretende realmente revogar este acesso concedido? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -122,7 +122,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -190,11 +190,11 @@ Moeda apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -322,7 +322,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -374,7 +374,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -458,7 +458,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -546,7 +546,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -718,7 +718,7 @@ Deseja realmente excluir este utilizador? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -765,6 +765,18 @@ 186 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Comparar com... @@ -802,7 +814,7 @@ Referência apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1126,7 +1138,7 @@ Por favor, insira o valor do seu fundo de emergência: apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -1170,7 +1182,7 @@ Setor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1182,7 +1194,7 @@ País apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -1202,11 +1214,11 @@ Setores apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1222,11 +1234,11 @@ Países apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1302,7 +1314,7 @@ AATD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1314,7 +1326,7 @@ 1A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1326,7 +1338,7 @@ 5A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1338,7 +1350,7 @@ Máx apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1386,7 +1398,7 @@ OK apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -1454,7 +1466,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1466,7 +1478,7 @@ Por favor, insira o seu código de cupão: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -1474,7 +1486,7 @@ Não foi possível resgatar o código de cupão apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -1482,7 +1494,7 @@ Código de cupão foi resgatado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -1490,7 +1502,7 @@ Atualizar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -1574,7 +1586,7 @@ Localidade apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1922,7 +1934,7 @@ Visão geral apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -1958,7 +1970,7 @@ Mercados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -2014,7 +2026,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -2062,7 +2074,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -2094,7 +2106,7 @@ Nome, símbolo or ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2126,7 +2138,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2786,11 +2798,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2850,7 +2862,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -3102,7 +3114,7 @@ Mapeamento de Símbolo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -3218,11 +3230,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3585,6 +3597,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. É gratuito. @@ -3798,7 +3822,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -3814,11 +3838,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3829,6 +3853,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Deseja mesmo eliminar esta plataforma? @@ -4186,7 +4218,7 @@ Configuração do raspador apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -4729,6 +4761,18 @@ 7 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Descubra as últimas atualizações e insights do Ghostfolio sobre finanças pessoais @@ -5712,7 +5756,7 @@ O preço de mercado atual é apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5720,7 +5764,7 @@ Teste apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5880,7 +5924,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5900,7 +5944,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5948,7 +5992,7 @@ ano apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5968,7 +6012,7 @@ anos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5988,7 +6032,7 @@ Coleta de dados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6213,7 +6257,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6613,7 +6657,7 @@ Erro apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6661,11 +6705,11 @@ Cancelar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6717,7 +6761,7 @@ Fechar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7175,7 +7219,7 @@ Não foi possível gerar uma chave de API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7183,7 +7227,7 @@ Defina esta chave de API no seu ambiente auto-hospedado: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7191,7 +7235,7 @@ Chave de API do Provedor de Dados do Ghostfolio Premium apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7199,7 +7243,7 @@ Você realmente deseja gerar uma nova chave de API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7239,7 +7283,7 @@ Guardar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7259,7 +7303,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7331,7 +7375,7 @@ O link foi copiado para a área de transferência apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7339,7 +7383,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Preço de mercado padrão apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Apply apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 357fb9f83..cd713e26a 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -275,7 +275,7 @@ Bu erişim iznini geri almayı gerçekten istiyor musunuz? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -319,7 +319,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -387,11 +387,11 @@ Para Birimi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -503,7 +503,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -555,7 +555,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -639,7 +639,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -763,7 +763,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -835,7 +835,7 @@ Sektör apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -847,7 +847,7 @@ Ülke apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -867,11 +867,11 @@ Sektörler apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -887,11 +887,11 @@ Ülkeler apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -903,7 +903,7 @@ Sembol Eşleştirme apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -919,7 +919,7 @@ Veri Toplayıcı Yapılandırması apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -927,7 +927,7 @@ Not apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -951,7 +951,7 @@ Ad, sembol ya da ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1119,11 +1119,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1134,6 +1134,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? Bu platformu silmeyi gerçekten istiyor musunuz? @@ -1163,7 +1171,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -1187,7 +1195,7 @@ Bu kullanıcıyı silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -1250,6 +1258,18 @@ 254 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Karşılaştır... @@ -1295,7 +1315,7 @@ Karşılaştırma Ölçütü apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1499,7 +1519,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1723,7 +1743,7 @@ Lütfen acil durum yedeği meblağını giriniz: apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -1967,7 +1987,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1979,7 +1999,7 @@ 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1991,7 +2011,7 @@ 5Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2003,7 +2023,7 @@ Maks. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2051,7 +2071,7 @@ Tamam apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -2287,7 +2307,7 @@ Özet apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -2426,6 +2446,18 @@ 225 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Son Ghostfolio güncellemelerini ve kişisel finans hakkındaki en son bilgileri keşfedin. @@ -2675,7 +2707,7 @@ Piyasalar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -3095,7 +3127,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -3874,6 +3906,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. Ücretsiz. @@ -4300,7 +4344,7 @@ Otomatik apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -4312,7 +4356,7 @@ Lütfen kupon kodunuzu girin: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -4320,7 +4364,7 @@ Kupon kodu kullanılamadı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -4328,7 +4372,7 @@ Kupon kodu kullanıldı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -4336,7 +4380,7 @@ Yeniden Yükle apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -4432,7 +4476,7 @@ Yerel Ayarlar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -4788,11 +4832,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4820,11 +4864,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4992,7 +5036,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -5712,7 +5756,7 @@ Şu anki piyasa fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5720,7 +5764,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5880,7 +5924,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5900,7 +5944,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5948,7 +5992,7 @@ Yıl apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5968,7 +6012,7 @@ Yıllar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5988,7 +6032,7 @@ Veri Toplama apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6213,7 +6257,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6613,7 +6657,7 @@ Hata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6661,11 +6705,11 @@ İptal Et apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6717,7 +6761,7 @@ Kapat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7175,7 +7219,7 @@ API anahtarı oluşturulamadı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7183,7 +7227,7 @@ Bu API anahtarını kendi barındırılan ortamınıza ayarlayın: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7191,7 +7235,7 @@ Ghostfolio Premium Veri Sağlayıcı API Anahtarı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7199,7 +7243,7 @@ Yeni bir API anahtarı oluşturmak istediğinize emin misiniz? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7239,7 +7283,7 @@ Kaydet apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7259,7 +7303,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7331,7 +7375,7 @@ Bağlantı panoya kopyalandı apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7339,7 +7383,7 @@ Tembel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Anında apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Varsayılan Piyasa Fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Mod apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Seçici apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ HTTP İstek Başlıkları apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ gün sonu apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ gerçek zamanlı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Güvenlik belirteci apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Bu kullanıcı için yeni bir güvenlik belirteci oluşturmak istediğinize emin misiniz? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ Güncelleştirilirken bir hata oluştu (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Uygula apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 3375f4e2c..c38c8d417 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -371,7 +371,7 @@ Посилання скопійовано в буфер обміну apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -387,7 +387,7 @@ Ви дійсно хочете відкликати цей наданий доступ? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -451,7 +451,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -519,11 +519,11 @@ Валюта apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -635,7 +635,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -711,7 +711,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -735,7 +735,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -915,7 +915,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1011,7 +1011,7 @@ Помилка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -1019,7 +1019,7 @@ Поточна ринкова ціна apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -1035,7 +1035,7 @@ Сектор apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1047,7 +1047,7 @@ Країна apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -1067,11 +1067,11 @@ Сектори apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1087,11 +1087,11 @@ Країни apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1103,7 +1103,7 @@ Зіставлення символів apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -1119,7 +1119,7 @@ Конфігурація скребка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -1127,7 +1127,7 @@ Тест apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -1135,11 +1135,11 @@ URL apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1150,12 +1150,20 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Note Примітка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1203,7 +1211,7 @@ Назва, символ або ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1311,7 +1319,7 @@ Збір даних apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1407,7 +1415,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -1615,7 +1623,7 @@ Ви дійсно хочете видалити цього користувача? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -1666,6 +1674,18 @@ 254 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... Порівняти з... @@ -1711,7 +1731,7 @@ Порівняльний показник apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2071,7 +2091,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -2275,7 +2295,7 @@ Зберегти apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2295,7 +2315,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -2323,7 +2343,7 @@ Будь ласка, встановіть суму вашого резервного фонду. apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -2555,7 +2575,7 @@ З початку року apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -2567,7 +2587,7 @@ 1 рік apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2579,7 +2599,7 @@ 5 років apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2591,7 +2611,7 @@ Максимум apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2695,7 +2715,7 @@ Не вдалося згенерувати ключ API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -2703,7 +2723,7 @@ ОК apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -2719,7 +2739,7 @@ Встановіть цей ключ API у вашому self-hosted середовищі: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -2727,7 +2747,7 @@ Ключ API Ghostfolio Premium Data Provider apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -2735,7 +2755,7 @@ Ви дійсно хочете згенерувати новий ключ API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -2743,7 +2763,7 @@ Не вдалося обміняти код купона apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -2751,7 +2771,7 @@ Код купона був обміняний apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -2759,7 +2779,7 @@ Перезавантажити apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -2767,7 +2787,7 @@ Будь ласка, введіть ваш код купона. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -2811,7 +2831,7 @@ Автоматичний apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2839,7 +2859,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -2895,7 +2915,7 @@ Локалізація apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3335,7 +3355,7 @@ Огляд apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -3482,6 +3502,18 @@ 225 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance Відкрийте для себе останні оновлення Ghostfolio та виявлення особистих фінансів @@ -3744,7 +3776,7 @@ Ринки apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -4256,7 +4288,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -5175,6 +5207,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. Це безкоштовно. @@ -6119,7 +6163,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -6139,7 +6183,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -6159,7 +6203,7 @@ рік apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6179,7 +6223,7 @@ роки apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -6443,11 +6487,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6475,11 +6519,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6507,11 +6551,11 @@ Скасувати apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6571,7 +6615,7 @@ Закрити apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6755,7 +6799,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7339,7 +7383,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7347,7 +7391,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7355,7 +7399,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7363,7 +7407,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7371,7 +7415,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7379,7 +7423,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7387,7 +7431,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7395,7 +7439,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7543,7 +7587,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7555,7 +7599,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7628,7 +7672,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7636,7 +7680,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7644,7 +7688,7 @@ Apply apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7987,7 +8031,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 9b0db6cff..6888369ab 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -282,7 +282,7 @@ Do you really want to revoke this granted access? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -341,7 +341,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -407,11 +407,11 @@ Currency apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -520,7 +520,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -573,7 +573,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -588,7 +588,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -761,7 +761,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -869,7 +869,7 @@ Sector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -880,7 +880,7 @@ Country apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -899,11 +899,11 @@ Sectors apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -918,11 +918,11 @@ Countries apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -933,7 +933,7 @@ Symbol Mapping apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -947,14 +947,14 @@ Scraper Configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -990,7 +990,7 @@ Name, symbol or ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1139,11 +1139,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1154,6 +1154,13 @@ 25 + + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? @@ -1179,7 +1186,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -1243,7 +1250,7 @@ Do you really want to delete this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -1300,6 +1307,17 @@ 254 + + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... @@ -1341,7 +1359,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1526,7 +1544,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1742,7 +1760,7 @@ Please set the amount of your emergency fund. apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -1957,7 +1975,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -1968,7 +1986,7 @@ 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1979,7 +1997,7 @@ 5Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1990,7 +2008,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2022,28 +2040,28 @@ Please enter your coupon code. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 Could not redeem coupon code apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 Coupon code has been redeemed apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 Reload apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -2083,7 +2101,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2129,7 +2147,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2276,7 +2294,7 @@ Okay apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -2523,7 +2541,7 @@ Overview apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -2661,6 +2679,17 @@ 225 + + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance @@ -2877,7 +2906,7 @@ Markets apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -3319,7 +3348,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -4037,6 +4066,17 @@ 288 + + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. @@ -4703,11 +4743,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4734,11 +4774,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4889,7 +4929,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -5230,14 +5270,14 @@ The current market price is apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5387,7 +5427,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5398,7 +5438,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5434,7 +5474,7 @@ year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5453,7 +5493,7 @@ years apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -5494,7 +5534,7 @@ Data Gathering apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -5670,7 +5710,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6027,18 +6067,18 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 Cancel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6117,7 +6157,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6551,28 +6591,28 @@ Could not generate an API key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 Set this API key in your self-hosted environment: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -6593,7 +6633,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6613,7 +6653,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -6679,63 +6719,63 @@ Link has been copied to the clipboard apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -6867,7 +6907,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -6881,7 +6921,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -6943,21 +6983,21 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 Apply apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7242,7 +7282,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index afa49fefd..02f90631d 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -304,7 +304,7 @@ 您真的要撤销此访问权限吗? apps/client/src/app/components/access-table/access-table.component.ts - 115 + 113 @@ -368,7 +368,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 311 + 309 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -436,11 +436,11 @@ 货币 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 201 + 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 318 + 316 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -552,7 +552,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 86 + 87 apps/client/src/app/components/admin-overview/admin-overview.html @@ -608,7 +608,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 574 + 448 @@ -624,7 +624,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 179 + 180 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -816,7 +816,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 219 + 217 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -912,7 +912,7 @@ 行业 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 264 + 262 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -924,7 +924,7 @@ 国家 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 275 + 273 apps/client/src/app/components/admin-users/admin-users.html @@ -944,11 +944,11 @@ 行业 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 281 + 279 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 396 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -964,11 +964,11 @@ 国家 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 291 + 289 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 533 + 407 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -980,7 +980,7 @@ 代码映射 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 386 + 384 @@ -996,7 +996,7 @@ 刮削配置 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 411 + 471 @@ -1004,7 +1004,7 @@ 笔记 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 558 + 432 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1044,7 +1044,7 @@ 名称、代码或 ISIN apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 132 + 133 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1212,11 +1212,11 @@ 网址 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 493 + 419 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 545 + 550 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1227,6 +1227,14 @@ 25 + + Asset profile has been saved + Asset profile has been saved + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 579 + + Do you really want to delete this platform? 您真的要删除这个平台吗? @@ -1256,7 +1264,7 @@ 当前年份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 @@ -1328,7 +1336,7 @@ 您真的要删除该用户吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 210 + 216 @@ -1391,6 +1399,18 @@ 254 + + Could not validate form + Could not validate form + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 555 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 558 + + Compare with... 与之比较... @@ -1436,7 +1456,7 @@ 基准 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 376 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1640,7 +1660,7 @@ 当前周 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 @@ -1876,7 +1896,7 @@ 请输入您的应急基金金额。 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 108 + 111 @@ -2108,7 +2128,7 @@ 年初至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 204 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -2120,7 +2140,7 @@ 1年 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2132,7 +2152,7 @@ 5年 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2144,7 +2164,7 @@ 最大限度 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 216 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2180,7 +2200,7 @@ 请输入您的优惠券代码。 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 218 + 210 @@ -2188,7 +2208,7 @@ 无法兑换优惠券代码 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 182 + 174 @@ -2196,7 +2216,7 @@ 优惠券代码已被兑换 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 195 + 187 @@ -2204,7 +2224,7 @@ 重新加载 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 196 + 188 @@ -2248,7 +2268,7 @@ 自动 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 69 + 70 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2300,7 +2320,7 @@ 语言环境 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 509 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2464,7 +2484,7 @@ 好的 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 157 + 149 apps/client/src/app/core/http-response.interceptor.ts @@ -2732,7 +2752,7 @@ 概述 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 113 + 114 apps/client/src/app/components/header/header.component.html @@ -2871,6 +2891,18 @@ 225 + + Could not parse scraper configuration + Could not parse scraper configuration + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 510 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 513 + + Discover the latest Ghostfolio updates and insights on personal finance 了解最新的 Ghostfolio 更新和个人理财见解 @@ -3108,7 +3140,7 @@ 市场 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 380 + 378 apps/client/src/app/components/footer/footer.component.html @@ -3604,7 +3636,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 228 + 226 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -4399,6 +4431,18 @@ 288 + + Could not save asset profile + Could not save asset profile + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 589 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 592 + + It’s free. 免费。 @@ -5137,11 +5181,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 237 + 235 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 328 + 326 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5169,11 +5213,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 246 + 244 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 344 + 342 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5341,7 +5385,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 168 + 169 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -5721,7 +5765,7 @@ 当前市场价格为 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 672 + 707 @@ -5729,7 +5773,7 @@ 测试 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 568 @@ -5897,7 +5941,7 @@ 本月至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5909,7 +5953,7 @@ 本周至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 196 + 192 libs/ui/src/lib/assistant/assistant.component.ts @@ -5949,7 +5993,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 208 + 204 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5969,7 +6013,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 212 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -6014,7 +6058,7 @@ 数据收集 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 604 + 587 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6214,7 +6258,7 @@ 包含在 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 374 @@ -6614,7 +6658,7 @@ 错误 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 663 + 698 @@ -6662,11 +6706,11 @@ 取消 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 161 + 162 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 592 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6718,7 +6762,7 @@ 关闭 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 611 + 594 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7176,7 +7220,7 @@ 无法生成 API 密钥 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 144 + 136 @@ -7184,7 +7228,7 @@ 在您的自托管环境中设置此 API 密钥: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 151 @@ -7192,7 +7236,7 @@ Ghostfolio Premium 数据提供者 API 密钥 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 162 + 154 @@ -7200,7 +7244,7 @@ 您确定要生成新的 API 密钥吗? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 167 + 159 @@ -7240,7 +7284,7 @@ 保存 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 620 + 603 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7260,7 +7304,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts - 106 + 109 apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -7332,7 +7376,7 @@ 链接已复制到剪贴板 apps/client/src/app/components/access-table/access-table.component.ts - 101 + 99 @@ -7340,7 +7384,7 @@ 延迟 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7348,7 +7392,7 @@ 即时 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7356,7 +7400,7 @@ 默认市场价格 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 420 + 481 @@ -7364,7 +7408,7 @@ 模式 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 461 + 518 @@ -7372,7 +7416,7 @@ 选择器 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 477 + 534 @@ -7380,7 +7424,7 @@ HTTP 请求标头 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 433 + 494 @@ -7388,7 +7432,7 @@ 收盘 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 230 + 226 @@ -7396,7 +7440,7 @@ 实时 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 234 + 230 @@ -7544,7 +7588,7 @@ 安全令牌 apps/client/src/app/components/admin-users/admin-users.component.ts - 231 + 237 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7556,7 +7600,7 @@ 您确定要为此用户生成新的安全令牌吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 242 @@ -7629,7 +7673,7 @@ () 已在使用中。 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 599 + 634 @@ -7637,7 +7681,7 @@ 在更新到 () 时发生错误。 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 607 + 642 @@ -7645,7 +7689,7 @@ 应用 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 153 + 154 @@ -7988,7 +8032,7 @@ 当前月份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 200 + 196 From 0b1668dc4b0fe7772699b3d53bfe8004520c1861 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Jan 2026 11:22:45 +0100 Subject: [PATCH 137/157] Task/rename branch and title in extract locales GitHub action (#6142) * Rename branch and title --- .github/workflows/extract-locales.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/extract-locales.yml b/.github/workflows/extract-locales.yml index c17eac5b6..b8b79b946 100644 --- a/.github/workflows/extract-locales.yml +++ b/.github/workflows/extract-locales.yml @@ -33,8 +33,8 @@ jobs: uses: peter-evans/create-pull-request@v7 with: author: 'github-actions[bot] ' - branch: 'feature/update-locales' + branch: 'task/update-locales' commit-message: 'Update locales' delete-branch: true - title: 'Feature/update locales' + title: 'Task/update locales' token: ${{ secrets.GITHUB_TOKEN }} From 3943ca9f8818a7329ebd6489d663d8944f14dd12 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 3 Jan 2026 18:03:58 +0700 Subject: [PATCH 138/157] Feature/extend holdings endpoint to include performance with currency effects for cash positions (#5650) * Extend holdings endpoint to include performance with currency effects for cash positions * Update changelog --- CHANGELOG.md | 4 + apps/api/src/app/order/order.service.ts | 139 ++++++++- .../calculator/portfolio-calculator.ts | 23 +- .../roai/portfolio-calculator-cash.spec.ts | 290 ++++++++++++++++++ .../calculator/roai/portfolio-calculator.ts | 13 +- .../interfaces/portfolio-order.interface.ts | 2 +- .../transaction-point-symbol.interface.ts | 3 +- .../src/app/portfolio/portfolio.service.ts | 54 ++-- .../exchange-rate-data.service.mock.ts | 6 +- .../exchange-rate-data.service.ts | 4 +- .../exchange-rate-data.interface.ts | 5 + 11 files changed, 510 insertions(+), 33 deletions(-) create mode 100644 apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts create mode 100644 apps/api/src/services/exchange-rate-data/interfaces/exchange-rate-data.interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d5b26a07a..fa9cefbc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Extended the portfolio holdings to include performance with currency effects for cash positions + ### Changed - Integrated the endpoint to get all platforms (`GET api/v1/platforms`) into the create or update account dialog diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 001d43b7a..57fe5d3b6 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -1,7 +1,10 @@ +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; +import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; import { AssetProfileChangedEvent } from '@ghostfolio/api/events/asset-profile-changed.event'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; +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 { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; @@ -16,6 +19,7 @@ import { import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { ActivitiesResponse, + Activity, AssetProfileIdentifier, EnhancedSymbolProfile, Filter @@ -42,8 +46,10 @@ import { randomUUID } from 'node:crypto'; @Injectable() export class OrderService { public constructor( + private readonly accountBalanceService: AccountBalanceService, private readonly accountService: AccountService, private readonly dataGatheringService: DataGatheringService, + private readonly dataProviderService: DataProviderService, private readonly eventEmitter: EventEmitter2, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly prismaService: PrismaService, @@ -317,6 +323,111 @@ export class OrderService { return count; } + /** + * Generates synthetic orders for cash holdings based on account balance history. + * Treat currencies as assets with a fixed unit price of 1.0 (in their own currency) to allow + * performance tracking based on exchange rate fluctuations. + * + * @param cashDetails - The cash balance details. + * @param userCurrency - The base currency of the user. + * @param userId - The ID of the user. + * @returns A response containing the list of synthetic cash activities. + */ + public async getCashOrders({ + cashDetails, + userCurrency, + userId + }: { + cashDetails: CashDetails; + userCurrency: string; + userId: string; + }): Promise { + const activities: Activity[] = []; + + for (const account of cashDetails.accounts) { + const { balances } = await this.accountBalanceService.getAccountBalances({ + userCurrency, + userId, + filters: [{ id: account.id, type: 'ACCOUNT' }] + }); + + let currentBalance = 0; + let currentBalanceInBaseCurrency = 0; + + for (const balanceItem of balances) { + const syntheticActivityTemplate: Activity = { + userId, + accountId: account.id, + accountUserId: account.userId, + comment: account.name, + createdAt: new Date(balanceItem.date), + currency: account.currency, + date: new Date(balanceItem.date), + fee: 0, + feeInAssetProfileCurrency: 0, + feeInBaseCurrency: 0, + id: balanceItem.id, + isDraft: false, + quantity: 1, + SymbolProfile: { + activitiesCount: 0, + assetClass: AssetClass.LIQUIDITY, + assetSubClass: AssetSubClass.CASH, + countries: [], + createdAt: new Date(balanceItem.date), + currency: account.currency, + dataSource: + this.dataProviderService.getDataSourceForExchangeRates(), + holdings: [], + id: account.currency, + isActive: true, + name: account.currency, + sectors: [], + symbol: account.currency, + updatedAt: new Date(balanceItem.date) + }, + symbolProfileId: account.currency, + type: ActivityType.BUY, + unitPrice: 1, + unitPriceInAssetProfileCurrency: 1, + updatedAt: new Date(balanceItem.date), + valueInBaseCurrency: 0, + value: 0 + }; + + if (currentBalance < balanceItem.value) { + // BUY + activities.push({ + ...syntheticActivityTemplate, + quantity: balanceItem.value - currentBalance, + type: ActivityType.BUY, + value: balanceItem.value - currentBalance, + valueInBaseCurrency: + balanceItem.valueInBaseCurrency - currentBalanceInBaseCurrency + }); + } else if (currentBalance > balanceItem.value) { + // SELL + activities.push({ + ...syntheticActivityTemplate, + quantity: currentBalance - balanceItem.value, + type: ActivityType.SELL, + value: currentBalance - balanceItem.value, + valueInBaseCurrency: + currentBalanceInBaseCurrency - balanceItem.valueInBaseCurrency + }); + } + + currentBalance = balanceItem.value; + currentBalanceInBaseCurrency = balanceItem.valueInBaseCurrency; + } + } + + return { + activities, + count: activities.length + }; + } + public async getLatestOrder({ dataSource, symbol }: AssetProfileIdentifier) { return this.prismaService.order.findFirst({ orderBy: { @@ -610,6 +721,15 @@ export class OrderService { return { activities, count }; } + /** + * Retrieves all orders required for the portfolio calculator, including both standard asset orders + * and synthetic orders representing cash activities. + * + * @param filters - Optional filters to apply to the orders. + * @param userCurrency - The base currency of the user. + * @param userId - The ID of the user. + * @returns An object containing the combined list of activities and the total count. + */ @LogPerformance public async getOrdersForPortfolioCalculator({ filters, @@ -620,12 +740,29 @@ export class OrderService { userCurrency: string; userId: string; }) { - return this.getOrders({ + const nonCashOrders = await this.getOrders({ filters, userCurrency, userId, withExcludedAccountsAndActivities: false // TODO }); + + const cashDetails = await this.accountService.getCashDetails({ + filters, + userId, + currency: userCurrency + }); + + const cashOrders = await this.getCashOrders({ + cashDetails, + userCurrency, + userId + }); + + return { + activities: [...nonCashOrders.activities, ...cashOrders.activities], + count: nonCashOrders.count + cashOrders.count + }; } public async getStatisticsByCurrency( diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index ee4219b58..d2b3c0625 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -203,13 +203,19 @@ export abstract class PortfolioCalculator { let totalInterestWithCurrencyEffect = new Big(0); let totalLiabilitiesWithCurrencyEffect = new Big(0); - for (const { currency, dataSource, symbol } of transactionPoints[ - firstIndex - 1 - ].items) { - dataGatheringItems.push({ - dataSource, - symbol - }); + for (const { + assetSubClass, + currency, + dataSource, + symbol + } of transactionPoints[firstIndex - 1].items) { + // Gather data for all assets except CASH + if (assetSubClass !== 'CASH') { + dataGatheringItems.push({ + dataSource, + symbol + }); + } currencies[symbol] = currency; } @@ -933,6 +939,7 @@ export abstract class PortfolioCalculator { } of this.activities) { let currentTransactionPointItem: TransactionPointSymbol; + const assetSubClass = SymbolProfile.assetSubClass; const currency = SymbolProfile.currency; const dataSource = SymbolProfile.dataSource; const factor = getFactor(type); @@ -977,6 +984,7 @@ export abstract class PortfolioCalculator { } currentTransactionPointItem = { + assetSubClass, currency, dataSource, investment, @@ -995,6 +1003,7 @@ export abstract class PortfolioCalculator { }; } else { currentTransactionPointItem = { + assetSubClass, currency, dataSource, fee, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts new file mode 100644 index 000000000..db6e08151 --- /dev/null +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -0,0 +1,290 @@ +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; +import { AccountService } from '@ghostfolio/api/app/account/account.service'; +import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; +import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; +import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; +import { parseDate } from '@ghostfolio/common/helper'; +import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { DataSource } from '@prisma/client'; +import { randomUUID } from 'node:crypto'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service', + () => { + return { + ExchangeRateDataService: jest.fn().mockImplementation(() => { + return ExchangeRateDataServiceMock; + }) + }; + } +); + +jest.mock( + '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', + () => { + return { + PortfolioSnapshotService: jest.fn().mockImplementation(() => { + return PortfolioSnapshotServiceMock; + }) + }; + } +); + +jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { + return { + RedisCacheService: jest.fn().mockImplementation(() => { + return RedisCacheServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let accountBalanceService: AccountBalanceService; + let accountService: AccountService; + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let dataProviderService: DataProviderService; + let exchangeRateDataService: ExchangeRateDataService; + let orderService: OrderService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeEach(() => { + configurationService = new ConfigurationService(); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + accountBalanceService = new AccountBalanceService( + null, + exchangeRateDataService, + null + ); + + accountService = new AccountService( + accountBalanceService, + null, + exchangeRateDataService, + null + ); + + redisCacheService = new RedisCacheService(null, configurationService); + + dataProviderService = new DataProviderService( + configurationService, + null, + null, + null, + null, + redisCacheService + ); + + currentRateService = new CurrentRateService( + dataProviderService, + null, + null, + null + ); + + orderService = new OrderService( + accountBalanceService, + accountService, + null, + dataProviderService, + null, + exchangeRateDataService, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('Cash Performance', () => { + it('should calculate performance for cash assets in CHF default currency', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2025-01-01').getTime()); + + const accountId = randomUUID(); + + jest + .spyOn(accountBalanceService, 'getAccountBalances') + .mockResolvedValue({ + balances: [ + { + accountId, + id: randomUUID(), + date: parseDate('2023-12-31'), + value: 1000, + valueInBaseCurrency: 850 + }, + { + accountId, + id: randomUUID(), + date: parseDate('2024-12-31'), + value: 2000, + valueInBaseCurrency: 1800 + } + ] + }); + + jest.spyOn(accountService, 'getCashDetails').mockResolvedValue({ + accounts: [ + { + balance: 2000, + comment: null, + createdAt: parseDate('2023-12-31'), + currency: 'USD', + id: accountId, + isExcluded: false, + name: 'USD', + platformId: null, + updatedAt: parseDate('2023-12-31'), + userId: userDummyData.id + } + ], + balanceInBaseCurrency: 1820 + }); + + jest + .spyOn(dataProviderService, 'getDataSourceForExchangeRates') + .mockReturnValue(DataSource.YAHOO); + + jest.spyOn(orderService, 'getOrders').mockResolvedValue({ + activities: [], + count: 0 + }); + + const { activities } = await orderService.getOrdersForPortfolioCalculator( + { + userCurrency: 'CHF', + userId: userDummyData.id + } + ); + + jest.spyOn(currentRateService, 'getValues').mockResolvedValue({ + dataProviderInfos: [], + errors: [], + values: [] + }); + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'CHF', + userId: userDummyData.id + }); + + const { historicalData } = await portfolioCalculator.computeSnapshot(); + + const historicalData20231231 = historicalData.find(({ date }) => { + return date === '2023-12-31'; + }); + const historicalData20240101 = historicalData.find(({ date }) => { + return date === '2024-01-01'; + }); + const historicalData20241231 = historicalData.find(({ date }) => { + return date === '2024-12-31'; + }); + + /** + * Investment value with currency effect: 1000 USD * 0.85 = 850 CHF + * Total investment: 1000 USD * 0.91 = 910 CHF + * Value (current): 1000 USD * 0.91 = 910 CHF + * Value with currency effect: 1000 USD * 0.85 = 850 CHF + */ + expect(historicalData20231231).toMatchObject({ + date: '2023-12-31', + investmentValueWithCurrencyEffect: 850, + netPerformance: 0, + netPerformanceInPercentage: 0, + netPerformanceInPercentageWithCurrencyEffect: 0, + netPerformanceWithCurrencyEffect: 0, + netWorth: 850, + totalAccountBalance: 0, + totalInvestment: 910, + totalInvestmentValueWithCurrencyEffect: 850, + value: 910, + valueWithCurrencyEffect: 850 + }); + + /** + * Net performance with currency effect: (1000 * 0.86) - (1000 * 0.85) = 10 CHF + * Total investment: 1000 USD * 0.91 = 910 CHF + * Total investment value with currency effect: 1000 USD * 0.85 = 850 CHF + * Value (current): 1000 USD * 0.91 = 910 CHF + * Value with currency effect: 1000 USD * 0.86 = 860 CHF + */ + expect(historicalData20240101).toMatchObject({ + date: '2024-01-01', + investmentValueWithCurrencyEffect: 0, + netPerformance: 0, + netPerformanceInPercentage: 0, + netPerformanceInPercentageWithCurrencyEffect: 0.011764705882352941, + netPerformanceWithCurrencyEffect: 10, + netWorth: 860, + totalAccountBalance: 0, + totalInvestment: 910, + totalInvestmentValueWithCurrencyEffect: 850, + value: 910, + valueWithCurrencyEffect: 860 + }); + + /** + * Investment value with currency effect: 1000 USD * 0.90 = 900 CHF + * Net performance: (1000 USD * 1.0) - (1000 USD * 1.0) = 0 CHF + * Net performance with currency effect: (1000 USD * 0.9) - (1000 USD * 0.85) = 50 CHF + * Total investment: 2000 USD * 0.91 = 1820 CHF + * Total investment value with currency effect: (1000 USD * 0.85) + (1000 USD * 0.90) = 1750 CHF + * Value (current): 2000 USD * 0.91 = 1820 CHF + * Value with currency effect: 2000 USD * 0.9 = 1800 CHF + */ + expect(historicalData20241231).toMatchObject({ + date: '2024-12-31', + investmentValueWithCurrencyEffect: 900, + netPerformance: 0, + netPerformanceInPercentage: 0, + netPerformanceInPercentageWithCurrencyEffect: 0.058823529411764705, + netPerformanceWithCurrencyEffect: 50, + netWorth: 1800, + totalAccountBalance: 0, + totalInvestment: 1820, + totalInvestmentValueWithCurrencyEffect: 1750, + value: 1820, + valueWithCurrencyEffect: 1800 + }); + }); + }); +}); 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 d4fad7d93..070d7543b 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -188,6 +188,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { }) ); + const isCash = orders[0]?.SymbolProfile?.assetSubClass === 'CASH'; + if (orders.length <= 0) { return { currentValues: {}, @@ -244,6 +246,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { // For BUY / SELL activities with a MANUAL data source where no historical market price is available, // the calculation should fall back to using the activity’s unit price. unitPriceAtEndDate = latestActivity.unitPrice; + } else if (isCash) { + unitPriceAtEndDate = new Big(1); } if ( @@ -295,7 +299,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { quantity: new Big(0), SymbolProfile: { dataSource, - symbol + symbol, + assetSubClass: isCash ? 'CASH' : undefined }, type: 'BUY', unitPrice: unitPriceAtStartDate @@ -308,7 +313,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { itemType: 'end', SymbolProfile: { dataSource, - symbol + symbol, + assetSubClass: isCash ? 'CASH' : undefined }, quantity: new Big(0), type: 'BUY', @@ -348,7 +354,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { quantity: new Big(0), SymbolProfile: { dataSource, - symbol + symbol, + assetSubClass: isCash ? 'CASH' : undefined }, type: 'BUY', unitPrice: marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice, 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 9362184c7..fcc8322fc 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-order.interface.ts @@ -6,7 +6,7 @@ export interface PortfolioOrder extends Pick { quantity: Big; SymbolProfile: Pick< Activity['SymbolProfile'], - 'currency' | 'dataSource' | 'name' | 'symbol' | 'userId' + 'assetSubClass' | 'currency' | 'dataSource' | 'name' | 'symbol' | 'userId' >; unitPrice: Big; } diff --git a/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts b/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts index f4ceadf3b..14e2e1f37 100644 --- a/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/transaction-point-symbol.interface.ts @@ -1,7 +1,8 @@ -import { DataSource, Tag } from '@prisma/client'; +import { AssetSubClass, DataSource, Tag } from '@prisma/client'; import { Big } from 'big.js'; export interface TransactionPointSymbol { + assetSubClass: AssetSubClass; averagePrice: Big; currency: string; dataSource: DataSource; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index faabee79b..5613af9e7 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -522,10 +522,6 @@ export class PortfolioService { return type === 'ACCOUNT'; }) ?? false; - const isFilteredByCash = filters?.some(({ id, type }) => { - return id === AssetClass.LIQUIDITY && type === 'ASSET_CLASS'; - }); - const isFilteredByClosedHoldings = filters?.some(({ id, type }) => { return id === 'CLOSED' && type === 'HOLDING_TYPE'; @@ -557,6 +553,9 @@ export class PortfolioService { assetProfileIdentifiers ); + const cashSymbolProfiles = this.getCashSymbolProfiles(cashDetails); + symbolProfiles.push(...cashSymbolProfiles); + const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {}; for (const symbolProfile of symbolProfiles) { symbolProfileMap[symbolProfile.symbol] = symbolProfile; @@ -661,18 +660,6 @@ export class PortfolioService { }; } - if (filters?.length === 0 || isFilteredByAccount || isFilteredByCash) { - const cashPositions = this.getCashPositions({ - cashDetails, - userCurrency, - value: filteredValueInBaseCurrency - }); - - for (const symbol of Object.keys(cashPositions)) { - holdings[symbol] = cashPositions[symbol]; - } - } - const { accounts, platforms } = await this.getValueOfAccountsAndPlatforms({ activities, filters, @@ -1548,6 +1535,37 @@ export class PortfolioService { return cashPositions; } + private getCashSymbolProfiles(cashDetails: CashDetails) { + const cashSymbols = [ + ...new Set(cashDetails.accounts.map(({ currency }) => currency)) + ]; + + return cashSymbols.map((currency) => { + const account = cashDetails.accounts.find( + ({ currency: accountCurrency }) => { + return accountCurrency === currency; + } + ); + + return { + currency, + activitiesCount: 0, + assetClass: AssetClass.LIQUIDITY, + assetSubClass: AssetSubClass.CASH, + countries: [], + createdAt: account.createdAt, + dataSource: DataSource.MANUAL, + holdings: [], + id: currency, + isActive: true, + name: currency, + sectors: [], + symbol: currency, + updatedAt: account.updatedAt + }; + }); + } + private getDividendsByGroup({ dividends, groupBy @@ -2158,7 +2176,7 @@ export class PortfolioService { accounts[account?.id || UNKNOWN_KEY] = { balance: 0, currency: account?.currency, - name: account.name, + name: account?.name, valueInBaseCurrency: currentValueOfSymbolInBaseCurrency }; } @@ -2172,7 +2190,7 @@ export class PortfolioService { platforms[account?.platformId || UNKNOWN_KEY] = { balance: 0, currency: account?.currency, - name: account.platform?.name, + name: account?.platform?.name, valueInBaseCurrency: currentValueOfSymbolInBaseCurrency }; } diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts index 076375523..857c1b5a5 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts @@ -14,7 +14,11 @@ export const ExchangeRateDataServiceMock = { '2017-12-31': 0.9787, '2018-01-01': 0.97373, '2023-01-03': 0.9238, - '2023-07-10': 0.8854 + '2023-07-10': 0.8854, + '2023-12-31': 0.85, + '2024-01-01': 0.86, + '2024-12-31': 0.9, + '2025-01-01': 0.91 } }); } else if (targetCurrency === 'EUR') { diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts index 8c1ba5b41..024bdf4e1 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts @@ -26,6 +26,8 @@ import { import { isNumber } from 'lodash'; import ms from 'ms'; +import { ExchangeRatesByCurrency } from './interfaces/exchange-rate-data.interface'; + @Injectable() export class ExchangeRateDataService { private currencies: string[] = []; @@ -59,7 +61,7 @@ export class ExchangeRateDataService { endDate?: Date; startDate: Date; targetCurrency: string; - }) { + }): Promise { if (!startDate) { return {}; } diff --git a/apps/api/src/services/exchange-rate-data/interfaces/exchange-rate-data.interface.ts b/apps/api/src/services/exchange-rate-data/interfaces/exchange-rate-data.interface.ts new file mode 100644 index 000000000..8e0d2c0d4 --- /dev/null +++ b/apps/api/src/services/exchange-rate-data/interfaces/exchange-rate-data.interface.ts @@ -0,0 +1,5 @@ +export interface ExchangeRatesByCurrency { + [currency: string]: { + [dateString: string]: number; + }; +} From 5cb82d59f21c86e69612ab9cc68941ba0b10908c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:26:47 +0100 Subject: [PATCH 139/157] Bugfix/header alignment in accounts table on mobile (#6143) * Fix header alignment * Update changelog --- CHANGELOG.md | 4 ++++ libs/ui/src/lib/accounts-table/accounts-table.component.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9cefbc6..75b331d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extracted the scraper configuration to a dedicated tab in the asset profile details dialog of the admin control panel - Improved the language localization for German (`de`) +### Fixed + +- Improved the table headers’ alignment of the accounts table on mobile + ## 2.227.0 - 2026-01-02 ### Changed diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html index d127b4bf3..be17c3684 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -199,7 +199,7 @@ From ec783568f536776961d8b2336ca69b539b342026 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:32:28 +0100 Subject: [PATCH 140/157] Task/upgrade @date-fns/utc to version 2.1.1 (#6141) * Upgrade @date-fns/utc to version 2.1.1 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b331d0f..48155d64a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Integrated the endpoint to get all platforms (`GET api/v1/platforms`) into the create or update account dialog - Extracted the scraper configuration to a dedicated tab in the asset profile details dialog of the admin control panel - Improved the language localization for German (`de`) +- Upgraded `@date-fns/utc` from version `2.1.0` to `2.1.1` ### Fixed diff --git a/package-lock.json b/package-lock.json index 05e735609..5b404e5f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@angular/router": "21.0.6", "@angular/service-worker": "21.0.6", "@codewithdan/observable-store": "2.2.15", - "@date-fns/utc": "2.1.0", + "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.5", "@ionic/angular": "8.7.8", "@keyv/redis": "4.4.0", @@ -3815,9 +3815,9 @@ } }, "node_modules/@date-fns/utc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.0.tgz", - "integrity": "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.1.tgz", + "integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA==", "license": "MIT" }, "node_modules/@deno/shim-deno": { diff --git a/package.json b/package.json index 1e82ec439..89c51ed99 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@angular/router": "21.0.6", "@angular/service-worker": "21.0.6", "@codewithdan/observable-store": "2.2.15", - "@date-fns/utc": "2.1.0", + "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.5", "@ionic/angular": "8.7.8", "@keyv/redis": "4.4.0", From 2cd23da4e81ce41ad8b4cb659b19654a5c528f1e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:37:23 +0100 Subject: [PATCH 141/157] Release 2.228.0 (#6146) --- 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 48155d64a..67a756af8 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 +## 2.228.0 - 2026-01-03 ### Added diff --git a/package-lock.json b/package-lock.json index 5b404e5f3..95b71adf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.227.0", + "version": "2.228.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.227.0", + "version": "2.228.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 89c51ed99..67eb101e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.227.0", + "version": "2.228.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 3698c76972ba0104bec842901855a99ab780f4ee Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 3 Jan 2026 20:44:14 +0100 Subject: [PATCH 142/157] Task/deprecate activities in portfolio holding response (#6147) * Deprecate activities * Update changelog --- CHANGELOG.md | 6 ++++++ .../responses/portfolio-holding-response.interface.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a756af8..124d03113 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 + +- Deprecated `activities` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` + ## 2.228.0 - 2026-01-03 ### Added diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts index 31f027ee9..4ec42933a 100644 --- a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts @@ -9,7 +9,9 @@ import { import { Tag } from '@prisma/client'; export interface PortfolioHoldingResponse { + /** @deprecated */ activities: Activity[]; + activitiesCount: number; averagePrice: number; dataProviderInfo: DataProviderInfo; From 601008e0e682a22ecb33737e1acb857972595fbc Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 4 Jan 2026 14:51:53 +0700 Subject: [PATCH 143/157] Task/move data service to UI library (#6154) * feat(lib): move data service * feat(client): update imports * feat(lib): update imports * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/app.component.ts | 2 +- .../account-detail-dialog/account-detail-dialog.component.ts | 2 +- .../admin-market-data/admin-market-data.component.ts | 2 +- .../asset-profile-dialog/asset-profile-dialog.component.ts | 2 +- .../create-asset-profile-dialog.component.ts | 2 +- .../app/components/admin-overview/admin-overview.component.ts | 2 +- .../app/components/admin-platform/admin-platform.component.ts | 2 +- .../app/components/admin-settings/admin-settings.component.ts | 2 +- .../client/src/app/components/admin-tag/admin-tag.component.ts | 2 +- .../src/app/components/admin-users/admin-users.component.ts | 2 +- .../data-provider-status/data-provider-status.component.ts | 2 +- apps/client/src/app/components/header/header.component.ts | 2 +- .../holding-detail-dialog/holding-detail-dialog.component.ts | 2 +- .../app/components/home-holdings/home-holdings.component.ts | 2 +- .../src/app/components/home-market/home-market.component.ts | 2 +- .../app/components/home-overview/home-overview.component.ts | 2 +- .../src/app/components/home-summary/home-summary.component.ts | 2 +- .../app/components/home-watchlist/home-watchlist.component.ts | 2 +- apps/client/src/app/components/markets/markets.component.ts | 2 +- .../create-or-update-access-dialog.component.ts | 2 +- .../user-account-access/user-account-access.component.ts | 2 +- .../user-account-membership.component.ts | 2 +- .../user-account-settings/user-account-settings.component.ts | 2 +- apps/client/src/app/core/auth.guard.ts | 2 +- apps/client/src/app/core/http-response.interceptor.ts | 2 +- apps/client/src/app/pages/about/about-page.component.ts | 2 +- .../app/pages/about/overview/about-overview-page.component.ts | 2 +- apps/client/src/app/pages/accounts/accounts-page.component.ts | 2 +- .../create-or-update-account-dialog.component.ts | 2 +- apps/client/src/app/pages/blog/blog-page.component.ts | 2 +- apps/client/src/app/pages/demo/demo-page.component.ts | 2 +- apps/client/src/app/pages/faq/faq-page.component.ts | 2 +- apps/client/src/app/pages/features/features-page.component.ts | 2 +- apps/client/src/app/pages/landing/landing-page.component.ts | 2 +- apps/client/src/app/pages/open/open-page.component.ts | 2 +- .../pages/portfolio/activities/activities-page.component.ts | 2 +- .../create-or-update-activity-dialog.component.ts | 2 +- .../import-activities-dialog.component.ts | 2 +- .../pages/portfolio/allocations/allocations-page.component.ts | 2 +- .../app/pages/portfolio/analysis/analysis-page.component.ts | 2 +- .../client/src/app/pages/portfolio/fire/fire-page.component.ts | 2 +- .../src/app/pages/portfolio/x-ray/x-ray-page.component.ts | 2 +- apps/client/src/app/pages/pricing/pricing-page.component.ts | 2 +- apps/client/src/app/pages/public/public-page.component.ts | 2 +- apps/client/src/app/pages/register/register-page.component.ts | 2 +- .../user-account-registration-dialog.component.ts | 2 +- .../pages/resources/glossary/resources-glossary.component.ts | 2 +- .../resources/personal-finance-tools/product-page.component.ts | 2 +- apps/client/src/app/services/admin.service.ts | 2 +- libs/ui/src/lib/assistant/assistant.component.ts | 2 +- .../benchmark-detail-dialog.component.ts | 3 +-- .../historical-market-data-editor-dialog.component.ts | 2 +- .../historical-market-data-editor.component.ts | 3 +-- .../src/app => libs/ui/src/lib}/services/data.service.ts | 0 libs/ui/src/lib/services/index.ts | 1 + .../lib/symbol-autocomplete/symbol-autocomplete.component.ts | 3 +-- 57 files changed, 56 insertions(+), 57 deletions(-) rename {apps/client/src/app => libs/ui/src/lib}/services/data.service.ts (100%) create mode 100644 libs/ui/src/lib/services/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 124d03113..d70d55644 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 - Deprecated `activities` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` +- Moved the data service to `@ghostfolio/ui/services` ## 2.228.0 - 2026-01-03 diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 12a7b0de9..a4af01124 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -4,6 +4,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; import { ColorScheme } from '@ghostfolio/common/types'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, @@ -36,7 +37,6 @@ import { GfFooterComponent } from './components/footer/footer.component'; import { GfHeaderComponent } from './components/header/header.component'; import { GfHoldingDetailDialogComponent } from './components/holding-detail-dialog/holding-detail-dialog.component'; import { HoldingDetailDialogParams } from './components/holding-detail-dialog/interfaces/interfaces'; -import { DataService } from './services/data.service'; import { ImpersonationStorageService } from './services/impersonation-storage.service'; import { TokenStorageService } from './services/token-storage.service'; import { UserService } from './services/user/user.service'; diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index d8f08ecc2..b40043cc8 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -1,5 +1,4 @@ import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos'; @@ -19,6 +18,7 @@ import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 4f1b60981..ebe35da3c 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -1,5 +1,4 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE, @@ -18,6 +17,7 @@ import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { SelectionModel } from '@angular/cdk/collections'; diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 07e060764..67dadc7b5 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -1,6 +1,5 @@ import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ASSET_CLASS_MAPPING, @@ -25,6 +24,7 @@ import { translate } from '@ghostfolio/ui/i18n'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; +import { DataService } from '@ghostfolio/ui/services'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index 32e1e3309..1087c11a1 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -1,10 +1,10 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { DEFAULT_CURRENCY, ghostfolioPrefix, PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; +import { DataService } from '@ghostfolio/ui/services'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index e4be7b062..101e60ec0 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -1,6 +1,5 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { CacheService } from '@ghostfolio/client/services/cache.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { PROPERTY_COUPONS, @@ -20,6 +19,7 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 832a70503..2843f059a 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,10 +1,10 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index cabf4e589..a4da22402 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -2,7 +2,6 @@ import { GfAdminPlatformComponent } from '@ghostfolio/client/components/admin-pl import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -16,6 +15,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index 305eb4628..ca7950291 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 1722b498f..affd4d61c 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,7 +1,6 @@ import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -21,6 +20,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts index 0f638961c..e44e81be9 100644 --- a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts +++ b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts @@ -1,4 +1,4 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index b7bf4cb98..9b003a590 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -1,7 +1,6 @@ import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces'; import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component'; import { LayoutService } from '@ghostfolio/client/core/layout.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { KEY_STAY_SIGNED_IN, @@ -18,6 +17,7 @@ import { GfAssistantComponent } from '@ghostfolio/ui/assistant/assistant.compone import { GfLogoComponent } from '@ghostfolio/ui/logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 6a7129fec..95c58d35a 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { NUMERICAL_PRECISION_THRESHOLD_3_FIGURES, @@ -26,6 +25,7 @@ import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical import { translate } from '@ghostfolio/ui/i18n'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; +import { DataService } from '@ghostfolio/ui/services'; import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector'; import { GfValueComponent } from '@ghostfolio/ui/value'; 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 33d9139ea..dc444977d 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 @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -11,6 +10,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { HoldingType, HoldingsViewMode } from '@ghostfolio/common/types'; import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table'; +import { DataService } from '@ghostfolio/ui/services'; import { GfToggleComponent } from '@ghostfolio/ui/toggle'; import { GfTreemapChartComponent } from '@ghostfolio/ui/treemap-chart'; 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 32d89f8e0..841c0818a 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 @@ -1,5 +1,4 @@ import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config'; import { resetHours } from '@ghostfolio/common/helper'; @@ -12,6 +11,7 @@ import { import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, diff --git a/apps/client/src/app/components/home-overview/home-overview.component.ts b/apps/client/src/app/components/home-overview/home-overview.component.ts index 6c9694a19..0d5020904 100644 --- a/apps/client/src/app/components/home-overview/home-overview.component.ts +++ b/apps/client/src/app/components/home-overview/home-overview.component.ts @@ -1,6 +1,5 @@ import { GfPortfolioPerformanceComponent } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.component'; import { LayoutService } from '@ghostfolio/client/core/layout.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config'; @@ -13,6 +12,7 @@ import { import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/components/home-summary/home-summary.component.ts b/apps/client/src/app/components/home-summary/home-summary.component.ts index 845d1b448..454d05689 100644 --- a/apps/client/src/app/components/home-summary/home-summary.component.ts +++ b/apps/client/src/app/components/home-summary/home-summary.component.ts @@ -1,5 +1,4 @@ import { GfPortfolioSummaryComponent } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -8,6 +7,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts index ab43e54dd..4adb4e54f 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -9,6 +8,7 @@ import { import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index a7f83216f..4b83e897f 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -1,5 +1,4 @@ import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { resetHours } from '@ghostfolio/common/helper'; import { @@ -12,6 +11,7 @@ import { import { FearAndGreedIndexMode } from '@ghostfolio/common/types'; import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; +import { DataService } from '@ghostfolio/ui/services'; import { GfToggleComponent } from '@ghostfolio/ui/toggle'; import { diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 05c047dc6..5c87b2f63 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -1,7 +1,7 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/user-account-access/user-account-access.component.ts b/apps/client/src/app/components/user-account-access/user-account-access.component.ts index 11960b8aa..ef78cccff 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,5 +1,4 @@ import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateAccessDto } from '@ghostfolio/common/dtos'; @@ -8,6 +7,7 @@ import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, 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 8eec9c188..92fd0d590 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 @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFormatString } from '@ghostfolio/common/helper'; @@ -8,6 +7,7 @@ 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'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { 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 e0028bb5c..c72c2a52d 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 @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { KEY_STAY_SIGNED_IN, KEY_TOKEN, @@ -13,6 +12,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/core/auth.guard.ts b/apps/client/src/app/core/auth.guard.ts index c26419031..123a6169a 100644 --- a/apps/client/src/app/core/auth.guard.ts +++ b/apps/client/src/app/core/auth.guard.ts @@ -1,7 +1,7 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { Injectable } from '@angular/core'; import { diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts index c2eef4175..ab99b440f 100644 --- a/apps/client/src/app/core/http-response.interceptor.ts +++ b/apps/client/src/app/core/http-response.interceptor.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { InfoItem } from '@ghostfolio/common/interfaces'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { HTTP_INTERCEPTORS, 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 4fe6a57e3..5ddb6b2e0 100644 --- a/apps/client/src/app/pages/about/about-page.component.ts +++ b/apps/client/src/app/pages/about/about-page.component.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { TabConfiguration, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, diff --git a/apps/client/src/app/pages/about/overview/about-overview-page.component.ts b/apps/client/src/app/pages/about/overview/about-overview-page.component.ts index d315f2cbd..bea19a1b9 100644 --- a/apps/client/src/app/pages/about/overview/about-overview-page.component.ts +++ b/apps/client/src/app/pages/about/overview/about-overview-page.component.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 2b496e4fb..6c8146f77 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -1,6 +1,5 @@ import { GfAccountDetailDialogComponent } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component'; import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -12,6 +11,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index ceb11a011..f4c68e70f 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { CreateAccountDto, UpdateAccountDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule, NgClass } from '@angular/common'; import { 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 9977e6df4..8a379a7e4 100644 --- a/apps/client/src/app/pages/blog/blog-page.component.ts +++ b/apps/client/src/app/pages/blog/blog-page.component.ts @@ -1,5 +1,5 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { DataService } from '@ghostfolio/ui/services'; import { Component, CUSTOM_ELEMENTS_SCHEMA, OnDestroy } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; diff --git a/apps/client/src/app/pages/demo/demo-page.component.ts b/apps/client/src/app/pages/demo/demo-page.component.ts index 9eba64788..5b94fd541 100644 --- a/apps/client/src/app/pages/demo/demo-page.component.ts +++ b/apps/client/src/app/pages/demo/demo-page.component.ts @@ -1,7 +1,7 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { InfoItem } from '@ghostfolio/common/interfaces'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { DataService } from '@ghostfolio/ui/services'; import { Component, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; 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 cba9e19f1..caf4217ca 100644 --- a/apps/client/src/app/pages/faq/faq-page.component.ts +++ b/apps/client/src/app/pages/faq/faq-page.component.ts @@ -1,7 +1,7 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { TabConfiguration } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { CUSTOM_ELEMENTS_SCHEMA, diff --git a/apps/client/src/app/pages/features/features-page.component.ts b/apps/client/src/app/pages/features/features-page.component.ts index dc2dfaf42..b9eb91fe2 100644 --- a/apps/client/src/app/pages/features/features-page.component.ts +++ b/apps/client/src/app/pages/features/features-page.component.ts @@ -1,9 +1,9 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index 55f07a798..25fb2d6e7 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -1,10 +1,10 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { Statistics } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfCarouselComponent } from '@ghostfolio/ui/carousel'; import { GfLogoComponent } from '@ghostfolio/ui/logo'; import { GfLogoCarouselComponent } from '@ghostfolio/ui/logo-carousel'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; diff --git a/apps/client/src/app/pages/open/open-page.component.ts b/apps/client/src/app/pages/open/open-page.component.ts index 773025769..6284c41f4 100644 --- a/apps/client/src/app/pages/open/open-page.component.ts +++ b/apps/client/src/app/pages/open/open-page.component.ts @@ -1,6 +1,6 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { Statistics, User } from '@ghostfolio/common/interfaces'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 5b5273b65..cf7a41215 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { IcsService } from '@ghostfolio/client/services/ics/ics.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -12,6 +11,7 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; 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 b44e8ee6b..8695f04ed 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 @@ -10,6 +10,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { translate } from '@ghostfolio/ui/i18n'; +import { DataService } from '@ghostfolio/ui/services'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -48,7 +49,6 @@ import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; import { EMPTY, Subject } from 'rxjs'; import { catchError, delay, takeUntil } from 'rxjs/operators'; -import { DataService } from '../../../../services/data.service'; import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; import { ActivityType } from './types/activity-type.type'; 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 582ab8e25..1a84e9f31 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 @@ -1,5 +1,4 @@ import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; import { CreateAccountWithBalancesDto, @@ -11,6 +10,7 @@ import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; +import { DataService } from '@ghostfolio/ui/services'; import { StepperOrientation, diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index b4de51701..70fa09eb1 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -1,6 +1,5 @@ import { GfAccountDetailDialogComponent } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component'; import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config'; @@ -17,6 +16,7 @@ import { Market, MarketAdvanced } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { GfTopHoldingsComponent } from '@ghostfolio/ui/top-holdings'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 63ed3569c..ec872c770 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -1,6 +1,5 @@ import { GfBenchmarkComparatorComponent } from '@ghostfolio/client/components/benchmark-comparator/benchmark-comparator.component'; import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -16,6 +15,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { AiPromptMode, GroupBy } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { GfToggleComponent } from '@ghostfolio/ui/toggle'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index da1378d22..27db6c76e 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -9,6 +8,7 @@ import { import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule, NgStyle } from '@angular/common'; diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts index 0bf869238..70b748b10 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -1,5 +1,4 @@ import { GfRulesComponent } from '@ghostfolio/client/components/rules/rules.component'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UpdateUserSettingDto } from '@ghostfolio/common/dtos'; @@ -10,6 +9,7 @@ import { import { User } from '@ghostfolio/common/interfaces/user.interface'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { NgClass } from '@angular/common'; import { ChangeDetectorRef, Component } from '@angular/core'; diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index aaf0c597b..f818e6b11 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -6,6 +5,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { translate } from '@ghostfolio/ui/i18n'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index 55e2a122a..fe4b295db 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -1,4 +1,3 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { prettifySymbol } from '@ghostfolio/common/helper'; import { @@ -11,6 +10,7 @@ import { Market } from '@ghostfolio/common/types'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table/activities-table.component'; import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table/holdings-table.component'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.component'; +import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; diff --git a/apps/client/src/app/pages/register/register-page.component.ts b/apps/client/src/app/pages/register/register-page.component.ts index d37a91f8e..21b26944e 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { InfoItem, LineChartItem } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfLogoComponent } from '@ghostfolio/ui/logo'; +import { DataService } from '@ghostfolio/ui/services'; import { Component, 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 36e1ce710..a7707ad3b 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 @@ -1,5 +1,5 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { ClipboardModule } from '@angular/cdk/clipboard'; import { TextFieldModule } from '@angular/cdk/text-field'; diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts index 96958dd70..112619239 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts @@ -1,7 +1,7 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { InfoItem } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; +import { DataService } from '@ghostfolio/ui/services'; import { Component, OnInit } from '@angular/core'; import { RouterModule } from '@angular/router'; diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts index 511cf672d..c8eff35be 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -1,8 +1,8 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; import { Product } from '@ghostfolio/common/interfaces'; import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { translate } from '@ghostfolio/ui/i18n'; +import { DataService } from '@ghostfolio/ui/services'; import { Component, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 10804aac9..a5f2ca24f 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -21,6 +21,7 @@ import { Filter } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; +import { DataService } from '@ghostfolio/ui/services'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; @@ -29,7 +30,6 @@ import { DataSource, MarketData, Platform } from '@prisma/client'; import { JobStatus } from 'bull'; import { environment } from '../../environments/environment'; -import { DataService } from './data.service'; @Injectable({ providedIn: 'root' diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 7bbc3978c..e3d597990 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -1,11 +1,11 @@ /* eslint-disable @nx/enforce-module-boundaries */ import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Filter, PortfolioPosition, User } from '@ghostfolio/common/interfaces'; import { InternalRoute } from '@ghostfolio/common/routes/interfaces/internal-route.interface'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { AccountWithPlatform, DateRange } from '@ghostfolio/common/types'; +import { DataService } from '@ghostfolio/ui/services'; import { FocusKeyManager } from '@angular/cdk/a11y'; import { diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts index 59c1e6e17..2f4c18288 100644 --- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { DataService } from '@ghostfolio/client/services/data.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails, @@ -7,6 +5,7 @@ import { } from '@ghostfolio/common/interfaces'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; +import { DataService } from '@ghostfolio/ui/services'; import { CUSTOM_ELEMENTS_SCHEMA, diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts index 21202981d..7383c4c9c 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts @@ -1,6 +1,6 @@ /* eslint-disable @nx/enforce-module-boundaries */ import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/client/services/data.service'; +import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts index f857e6e53..098f4e295 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts @@ -1,5 +1,3 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { DataService } from '@ghostfolio/client/services/data.service'; import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, @@ -7,6 +5,7 @@ import { getLocale } from '@ghostfolio/common/helper'; import { LineChartItem, User } from '@ghostfolio/common/interfaces'; +import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts similarity index 100% rename from apps/client/src/app/services/data.service.ts rename to libs/ui/src/lib/services/data.service.ts diff --git a/libs/ui/src/lib/services/index.ts b/libs/ui/src/lib/services/index.ts new file mode 100644 index 000000000..2ba773ede --- /dev/null +++ b/libs/ui/src/lib/services/index.ts @@ -0,0 +1 @@ +export * from './data.service'; diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index 05a2c06c3..11d6f09dc 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -1,7 +1,6 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { DataService } from '@ghostfolio/client/services/data.service'; import { LookupItem } from '@ghostfolio/common/interfaces'; import { GfSymbolPipe } from '@ghostfolio/common/pipes'; +import { DataService } from '@ghostfolio/ui/services'; import { FocusMonitor } from '@angular/cdk/a11y'; import { From 5101c406b463c395e1bca72cfadfee37c5b764fe Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 4 Jan 2026 09:12:25 +0100 Subject: [PATCH 144/157] Task/refactor dividend import (#6150) * Refactor dividend import * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/import/import.controller.ts | 1 + apps/api/src/app/import/import.module.ts | 2 + apps/api/src/app/import/import.service.ts | 73 ++++++++++++-------- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d70d55644..cf81cb29e 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 - Deprecated `activities` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Moved the data service to `@ghostfolio/ui/services` +- Refactored the dividend import ## 2.228.0 - 2026-01-03 diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 2681444df..81481fd65 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -103,6 +103,7 @@ export class ImportController { const activities = await this.importService.getDividends({ dataSource, symbol, + userCurrency: this.request.user.settings.settings.baseCurrency, userId: this.request.user.id }); diff --git a/apps/api/src/app/import/import.module.ts b/apps/api/src/app/import/import.module.ts index 88990af2e..a4a13f941 100644 --- a/apps/api/src/app/import/import.module.ts +++ b/apps/api/src/app/import/import.module.ts @@ -6,6 +6,7 @@ import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module' import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; +import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.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'; @@ -24,6 +25,7 @@ import { ImportService } from './import.service'; controllers: [ImportController], imports: [ AccountModule, + ApiModule, CacheModule, ConfigurationModule, DataGatheringModule, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 2deef1c44..3f8bd2cde 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -2,6 +2,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; +import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; @@ -25,7 +26,7 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { - AccountWithPlatform, + AccountWithValue, OrderWithAccount, UserWithSettings } from '@ghostfolio/common/types'; @@ -43,6 +44,7 @@ import { ImportDataDto } from './import-data.dto'; export class ImportService { public constructor( private readonly accountService: AccountService, + private readonly apiService: ApiService, private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, @@ -57,8 +59,12 @@ export class ImportService { public async getDividends({ dataSource, symbol, + userCurrency, userId - }: AssetProfileIdentifier & { userId: string }): Promise { + }: AssetProfileIdentifier & { + userCurrency: string; + userId: string; + }): Promise { try { const holding = await this.portfolioService.getHolding({ dataSource, @@ -71,36 +77,45 @@ export class ImportService { return []; } - const { activities, firstBuyDate, historicalData } = holding; + const filters = this.apiService.buildFiltersFromQueryParams({ + filterByDataSource: dataSource, + filterBySymbol: symbol + }); - const [[assetProfile], dividends] = await Promise.all([ - this.symbolProfileService.getSymbolProfiles([ - { - dataSource, - symbol - } - ]), - await this.dataProviderService.getDividends({ - dataSource, - symbol, - from: parseDate(firstBuyDate), - granularity: 'day', - to: new Date() - }) - ]); + const { firstBuyDate, historicalData } = holding; - const accounts = activities - .filter(({ account }) => { - return !!account; - }) - .map(({ account }) => { - return account; - }); + const [{ accounts }, { activities }, [assetProfile], dividends] = + await Promise.all([ + this.portfolioService.getAccountsWithAggregations({ + filters, + userId, + withExcludedAccounts: true + }), + this.orderService.getOrders({ + filters, + userCurrency, + userId, + startDate: parseDate(firstBuyDate) + }), + this.symbolProfileService.getSymbolProfiles([ + { + dataSource, + symbol + } + ]), + await this.dataProviderService.getDividends({ + dataSource, + symbol, + from: parseDate(firstBuyDate), + granularity: 'day', + to: new Date() + }) + ]); const account = this.isUniqueAccount(accounts) ? accounts[0] : undefined; return await Promise.all( - Object.entries(dividends).map(async ([dateString, { marketPrice }]) => { + Object.entries(dividends).map(([dateString, { marketPrice }]) => { const quantity = historicalData.find((historicalDataItem) => { return historicalDataItem.date === dateString; @@ -695,11 +710,11 @@ export class ImportService { ); } - private isUniqueAccount(accounts: AccountWithPlatform[]) { + private isUniqueAccount(accounts: AccountWithValue[]) { const uniqueAccountIds = new Set(); - for (const account of accounts) { - uniqueAccountIds.add(account.id); + for (const { id } of accounts) { + uniqueAccountIds.add(id); } return uniqueAccountIds.size === 1; From feb25c9266a066fb6c6a7ae142c49b1daee8389c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 4 Jan 2026 20:40:53 +0100 Subject: [PATCH 145/157] Bugfix/fix filtering by asset class in get holdings endpoint (#6151) * Fix filtering by asset class * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/app/order/order.service.ts | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf81cb29e..4e66bb4ec 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 - Moved the data service to `@ghostfolio/ui/services` - Refactored the dividend import +### Fixed + +- Fixed the filtering by asset class in the endpoint `GET api/v1/portfolio/holdings` + ## 2.228.0 - 2026-01-03 ### Added diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 57fe5d3b6..a939cb476 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -329,19 +329,39 @@ export class OrderService { * performance tracking based on exchange rate fluctuations. * * @param cashDetails - The cash balance details. + * @param filters - Optional filters to apply. * @param userCurrency - The base currency of the user. * @param userId - The ID of the user. * @returns A response containing the list of synthetic cash activities. */ public async getCashOrders({ cashDetails, + filters = [], userCurrency, userId }: { cashDetails: CashDetails; + filters?: Filter[]; userCurrency: string; userId: string; }): Promise { + const filtersByAssetClass = filters.filter(({ type }) => { + return type === 'ASSET_CLASS'; + }); + + if ( + filtersByAssetClass.length > 0 && + !filtersByAssetClass.find(({ id }) => { + return id === AssetClass.LIQUIDITY; + }) + ) { + // If asset class filters are present and none of them is liquidity, return an empty response + return { + activities: [], + count: 0 + }; + } + const activities: Activity[] = []; for (const account of cashDetails.accounts) { @@ -755,6 +775,7 @@ export class OrderService { const cashOrders = await this.getCashOrders({ cashDetails, + filters, userCurrency, userId }); From d4d6ce1d07a49c4b75366a93c42a0a3563f473bc Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:28:18 +0700 Subject: [PATCH 146/157] Task/move admin service to UI library (#6159) * Move admin service to UI library * Update changelog --- CHANGELOG.md | 1 + .../app/components/admin-jobs/admin-jobs.component.ts | 2 +- .../admin-market-data/admin-market-data.component.ts | 3 +-- .../admin-market-data/admin-market-data.service.ts | 2 +- .../asset-profile-dialog.component.ts | 3 +-- .../create-asset-profile-dialog.component.ts | 3 +-- .../admin-overview/admin-overview.component.ts | 3 +-- .../admin-platform/admin-platform.component.ts | 3 +-- .../admin-settings/admin-settings.component.ts | 3 +-- .../components/admin-users/admin-users.component.ts | 3 +-- .../user-detail-dialog/user-detail-dialog.component.ts | 2 +- apps/client/src/environments/environment.prod.ts | 4 +++- apps/client/src/environments/environment.ts | 4 +++- apps/client/src/main.ts | 5 +++++ eslint.config.cjs | 4 +--- libs/ui/src/lib/assistant/assistant.component.ts | 4 +--- libs/ui/src/lib/environment/environment.interface.ts | 5 +++++ libs/ui/src/lib/environment/environment.token.ts | 7 +++++++ libs/ui/src/lib/environment/index.ts | 2 ++ .../historical-market-data-editor-dialog.component.ts | 4 +--- .../app => libs/ui/src/lib}/services/admin.service.ts | 10 +++++----- libs/ui/src/lib/services/index.ts | 1 + 22 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 libs/ui/src/lib/environment/environment.interface.ts create mode 100644 libs/ui/src/lib/environment/environment.token.ts create mode 100644 libs/ui/src/lib/environment/index.ts rename {apps/client/src/app => libs/ui/src/lib}/services/admin.service.ts (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e66bb4ec..6fa5ef8b0 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 - Deprecated `activities` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` +- Moved the admin service to `@ghostfolio/ui/services` - Moved the data service to `@ghostfolio/ui/services` - Refactored the dividend import diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index 66bac76f5..de70a7b6e 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -1,4 +1,3 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DATA_GATHERING_QUEUE_PRIORITY_HIGH, @@ -9,6 +8,7 @@ import { import { getDateWithTimeFormatString } from '@ghostfolio/common/helper'; import { AdminJobs, User } from '@ghostfolio/common/interfaces'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { AdminService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index ebe35da3c..bc3b0d374 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -1,4 +1,3 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE, @@ -17,7 +16,7 @@ import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { translate } from '@ghostfolio/ui/i18n'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { SelectionModel } from '@angular/cdk/collections'; diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index eaad32c0e..9528687a8 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -1,4 +1,3 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { @@ -11,6 +10,7 @@ import { AdminMarketDataItem } from '@ghostfolio/common/interfaces'; import { NotificationService } from '@ghostfolio/ui/notifications'; +import { AdminService } from '@ghostfolio/ui/services'; import { Injectable } from '@angular/core'; import { EMPTY, catchError, finalize, forkJoin } from 'rxjs'; diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 67dadc7b5..cbd8deba3 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -1,5 +1,4 @@ import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ASSET_CLASS_MAPPING, @@ -24,7 +23,7 @@ import { translate } from '@ghostfolio/ui/i18n'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfValueComponent } from '@ghostfolio/ui/value'; diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts index 1087c11a1..6c180b034 100644 --- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts @@ -1,10 +1,9 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DEFAULT_CURRENCY, ghostfolioPrefix, PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 101e60ec0..6284f05fd 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -1,4 +1,3 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { CacheService } from '@ghostfolio/client/services/cache.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { @@ -19,7 +18,7 @@ import { } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { NotificationService } from '@ghostfolio/ui/notifications'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 2843f059a..02a2eed64 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,10 +1,9 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index a4da22402..446221058 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -1,7 +1,6 @@ import { GfAdminPlatformComponent } from '@ghostfolio/client/components/admin-platform/admin-platform.component'; import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -15,7 +14,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index affd4d61c..2ae3b1a57 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,6 +1,5 @@ import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -20,7 +19,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; 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 248fd48f3..cdf977058 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 @@ -1,7 +1,7 @@ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminUserResponse } from '@ghostfolio/common/interfaces'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; +import { AdminService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { diff --git a/apps/client/src/environments/environment.prod.ts b/apps/client/src/environments/environment.prod.ts index 6f1d6b92d..4ee0d508b 100644 --- a/apps/client/src/environments/environment.prod.ts +++ b/apps/client/src/environments/environment.prod.ts @@ -1,4 +1,6 @@ -export const environment = { +import type { GfEnvironment } from '@ghostfolio/ui/environment'; + +export const environment: GfEnvironment = { lastPublish: '{BUILD_TIMESTAMP}', production: true, stripePublicKey: '' diff --git a/apps/client/src/environments/environment.ts b/apps/client/src/environments/environment.ts index 0a4d51d98..ccedf6738 100644 --- a/apps/client/src/environments/environment.ts +++ b/apps/client/src/environments/environment.ts @@ -1,8 +1,10 @@ +import type { GfEnvironment } from '@ghostfolio/ui/environment'; + // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. -export const environment = { +export const environment: GfEnvironment = { lastPublish: null, production: false, stripePublicKey: '' diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index c15703645..f596de5f4 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -1,5 +1,6 @@ import { InfoResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; +import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment'; import { GfNotificationModule } from '@ghostfolio/ui/notifications'; import { Platform } from '@angular/cdk/platform'; @@ -89,6 +90,10 @@ import { environment } from './environments/environment'; provide: DateAdapter, useClass: CustomDateAdapter }, + { + provide: GF_ENVIRONMENT, + useValue: environment + }, { provide: MAT_DATE_FORMATS, useValue: DateFormats diff --git a/eslint.config.cjs b/eslint.config.cjs index 5962e261d..76d627d18 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -28,9 +28,7 @@ module.exports = [ onlyDependOnLibsWithTags: ['*'] } ], - enforceBuildableLibDependency: true, - // Temporary fix, should be removed eventually - ignoredCircularDependencies: [['client', 'ui']] + enforceBuildableLibDependency: true } ], '@typescript-eslint/no-extra-semi': 'error', diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index e3d597990..2b0216613 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -1,11 +1,9 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { AdminService } from '@ghostfolio/client/services/admin.service'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Filter, PortfolioPosition, User } from '@ghostfolio/common/interfaces'; import { InternalRoute } from '@ghostfolio/common/routes/interfaces/internal-route.interface'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { AccountWithPlatform, DateRange } from '@ghostfolio/common/types'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { FocusKeyManager } from '@angular/cdk/a11y'; import { diff --git a/libs/ui/src/lib/environment/environment.interface.ts b/libs/ui/src/lib/environment/environment.interface.ts new file mode 100644 index 000000000..9cb279515 --- /dev/null +++ b/libs/ui/src/lib/environment/environment.interface.ts @@ -0,0 +1,5 @@ +export interface GfEnvironment { + lastPublish: string | null; + production: boolean; + stripePublicKey: string; +} diff --git a/libs/ui/src/lib/environment/environment.token.ts b/libs/ui/src/lib/environment/environment.token.ts new file mode 100644 index 000000000..277e9c5e2 --- /dev/null +++ b/libs/ui/src/lib/environment/environment.token.ts @@ -0,0 +1,7 @@ +import { InjectionToken } from '@angular/core'; + +import { GfEnvironment } from './environment.interface'; + +export const GF_ENVIRONMENT = new InjectionToken( + 'GF_ENVIRONMENT' +); diff --git a/libs/ui/src/lib/environment/index.ts b/libs/ui/src/lib/environment/index.ts new file mode 100644 index 000000000..828eea646 --- /dev/null +++ b/libs/ui/src/lib/environment/index.ts @@ -0,0 +1,2 @@ +export * from './environment.interface'; +export * from './environment.token'; diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts index 7383c4c9c..7e7094dd3 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts @@ -1,6 +1,4 @@ -/* eslint-disable @nx/enforce-module-boundaries */ -import { AdminService } from '@ghostfolio/client/services/admin.service'; -import { DataService } from '@ghostfolio/ui/services'; +import { AdminService, DataService } from '@ghostfolio/ui/services'; import { ChangeDetectionStrategy, diff --git a/apps/client/src/app/services/admin.service.ts b/libs/ui/src/lib/services/admin.service.ts similarity index 94% rename from apps/client/src/app/services/admin.service.ts rename to libs/ui/src/lib/services/admin.service.ts index a5f2ca24f..145f134e3 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/libs/ui/src/lib/services/admin.service.ts @@ -21,22 +21,22 @@ import { Filter } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; +import { GF_ENVIRONMENT, GfEnvironment } from '@ghostfolio/ui/environment'; import { DataService } from '@ghostfolio/ui/services'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { SortDirection } from '@angular/material/sort'; import { DataSource, MarketData, Platform } from '@prisma/client'; import { JobStatus } from 'bull'; -import { environment } from '../../environments/environment'; - @Injectable({ providedIn: 'root' }) export class AdminService { public constructor( private dataService: DataService, + @Inject(GF_ENVIRONMENT) private environment: GfEnvironment, private http: HttpClient ) {} @@ -124,7 +124,7 @@ export class AdminService { }); return this.http.get( - `${environment.production ? 'https://ghostfol.io' : ''}/api/v2/data-providers/ghostfolio/status`, + `${this.environment.production ? 'https://ghostfol.io' : ''}/api/v2/data-providers/ghostfolio/status`, { headers } ); } @@ -276,7 +276,7 @@ export class AdminService { scraperConfiguration, symbol }: AssetProfileIdentifier & UpdateAssetProfileDto['scraperConfiguration']) { - return this.http.post( + return this.http.post<{ price: number }>( `/api/v1/admin/market-data/${dataSource}/${symbol}/test`, { scraperConfiguration diff --git a/libs/ui/src/lib/services/index.ts b/libs/ui/src/lib/services/index.ts index 2ba773ede..9cedba875 100644 --- a/libs/ui/src/lib/services/index.ts +++ b/libs/ui/src/lib/services/index.ts @@ -1 +1,2 @@ +export * from './admin.service'; export * from './data.service'; From 09e7e6182d97e3d541f00da4360daaf1a83a62ca Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:28:44 +0100 Subject: [PATCH 147/157] Task/refactor firstOrderDate to dateOfFirstActivity in portfolio service (#6161) * Refactor firstOrderDate to dateOfFirstActivity --- apps/api/src/app/portfolio/portfolio.service.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 5613af9e7..4bc10bd49 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1872,18 +1872,17 @@ export class PortfolioService { netPerformanceWithCurrencyEffect } = performance; - const dividendInBaseCurrency = - await portfolioCalculator.getDividendInBaseCurrency(); - const totalEmergencyFund = this.getTotalEmergencyFund({ emergencyFundHoldingsValueInBaseCurrency, userSettings: user.settings?.settings as UserSettings }); - const fees = await portfolioCalculator.getFeesInBaseCurrency(); + const dateOfFirstActivity = portfolioCalculator.getStartDate(); - const firstOrderDate = portfolioCalculator.getStartDate(); + const dividendInBaseCurrency = + await portfolioCalculator.getDividendInBaseCurrency(); + const fees = await portfolioCalculator.getFeesInBaseCurrency(); const interest = await portfolioCalculator.getInterestInBaseCurrency(); const liabilities = @@ -1941,7 +1940,7 @@ export class PortfolioService { .minus(liabilities) .toNumber(); - const daysInMarket = differenceInDays(new Date(), firstOrderDate); + const daysInMarket = differenceInDays(new Date(), dateOfFirstActivity); const annualizedPerformancePercent = getAnnualizedPerformancePercent({ daysInMarket, @@ -1960,6 +1959,7 @@ export class PortfolioService { annualizedPerformancePercent, annualizedPerformancePercentWithCurrencyEffect, cash, + dateOfFirstActivity, excludedAccountsAndActivities, netPerformance, netPerformancePercentage, @@ -1972,7 +1972,6 @@ export class PortfolioService { }).length, committedFunds: committedFunds.toNumber(), currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(), - dateOfFirstActivity: firstOrderDate, dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), emergencyFund: { assets: emergencyFundHoldingsValueInBaseCurrency, From ac67a6e190e505c964d4fbfb891d537ee679c905 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:27:48 +0100 Subject: [PATCH 148/157] Task/extend portfolio calculator cash test (#6162) * Extend test --- .../roai/portfolio-calculator-cash.spec.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index db6e08151..e27bb4daa 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -18,6 +18,7 @@ import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { DataSource } from '@prisma/client'; +import { Big } from 'big.js'; import { randomUUID } from 'node:crypto'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { @@ -207,17 +208,23 @@ describe('PortfolioCalculator', () => { userId: userDummyData.id }); - const { historicalData } = await portfolioCalculator.computeSnapshot(); + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - const historicalData20231231 = historicalData.find(({ date }) => { - return date === '2023-12-31'; - }); - const historicalData20240101 = historicalData.find(({ date }) => { - return date === '2024-01-01'; - }); - const historicalData20241231 = historicalData.find(({ date }) => { - return date === '2024-12-31'; - }); + const historicalData20231231 = portfolioSnapshot.historicalData.find( + ({ date }) => { + return date === '2023-12-31'; + } + ); + const historicalData20240101 = portfolioSnapshot.historicalData.find( + ({ date }) => { + return date === '2024-01-01'; + } + ); + const historicalData20241231 = portfolioSnapshot.historicalData.find( + ({ date }) => { + return date === '2024-12-31'; + } + ); /** * Investment value with currency effect: 1000 USD * 0.85 = 850 CHF @@ -285,6 +292,13 @@ describe('PortfolioCalculator', () => { value: 1820, valueWithCurrencyEffect: 1800 }); + + expect(portfolioSnapshot).toMatchObject({ + hasErrors: false, + totalFeesWithCurrencyEffect: new Big('0'), + totalInterestWithCurrencyEffect: new Big('0'), + totalLiabilitiesWithCurrencyEffect: new Big('0') + }); }); }); }); From a493afb52656199356b4e20ef66ec2b90b58a245 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:00:41 +0100 Subject: [PATCH 149/157] Task/set active sort column in accounts table component (#6175) * Set active sort column * Update changelog --- CHANGELOG.md | 1 + .../src/lib/accounts-table/accounts-table.component.html | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa5ef8b0..0cb727d58 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 +- Set the active sort column in the accounts table component - Deprecated `activities` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol` - Moved the admin service to `@ghostfolio/ui/services` - Moved the data service to `@ghostfolio/ui/services` diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.html b/libs/ui/src/lib/accounts-table/accounts-table.component.html index be17c3684..f76a5d676 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -13,7 +13,14 @@ }
- +
Date: Wed, 7 Jan 2026 17:21:02 +0100 Subject: [PATCH 150/157] Task/upgrade @types/lodash to version 4.17.21 (#6140) * Upgrade @types/lodash to version 4.17.21 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 95b71adf5..9b37e8439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,7 +125,7 @@ "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", "@types/jsonpath": "0.2.4", - "@types/lodash": "4.17.20", + "@types/lodash": "4.17.21", "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", @@ -12455,9 +12455,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 67eb101e1..140e4ce44 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", "@types/jsonpath": "0.2.4", - "@types/lodash": "4.17.20", + "@types/lodash": "4.17.21", "@types/node": "22.15.17", "@types/papaparse": "5.3.7", "@types/passport-google-oauth20": "2.0.16", From 4539367bc5f8e3ee18d303ff44b0ef9c55b41595 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:21:21 +0100 Subject: [PATCH 151/157] Task/refresh cryptocurrencies list 20260105 (#6172) * Update cryptocurrencies.json * Update changelog --- CHANGELOG.md | 1 + .../cryptocurrencies/cryptocurrencies.json | 152 +++++++++++++++--- 2 files changed, 132 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb727d58..b67aeea4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved the admin service to `@ghostfolio/ui/services` - Moved the data service to `@ghostfolio/ui/services` - Refactored the dividend import +- Refreshed the cryptocurrencies list ### Fixed diff --git a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json index 80c07fc64..5becbf2f9 100644 --- a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json +++ b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json @@ -22,6 +22,7 @@ "2015": "2015 coin", "2024": "2024", "2025": "2025 TOKEN", + "2026": "2026", "2049": "TOKEN 2049", "2192": "LERNITAS", "4444": "4444 Meme", @@ -83,6 +84,7 @@ "1UP": "Uptrennd", "1WO": "1World", "2022M": "2022MOON", + "2026MEMECLUB": "2026", "20EX": "20ex", "21BTC": "21.co Wrapped BTC", "21X": "21X", @@ -177,11 +179,13 @@ "AAI": "AutoAir AI", "AAPLX": "Apple xStock", "AAPX": "AMPnet", + "AARBWBTC": "Aave Arbitrum WBTC", "AARDY": "Baby Aardvark", "AARK": "Aark", "AART": "ALL.ART", "AAST": "AASToken", "AAT": "Agricultural Trade Chain", + "AAVAWBTC": "Aave aWBTC", "AAVE": "Aave", "AAVEE": "AAVE.e (Avalanche Bride)", "AAVEGOTCHIFOMO": "Aavegotchi FOMO", @@ -295,7 +299,8 @@ "ADEL": "Akropolis Delphi", "ADF": "Art de Finance", "ADH": "Adhive", - "ADI": "Aditus", + "ADI": "ADI", + "ADITUS": "Aditus", "ADIX": "Adix Token", "ADK": "Aidos Kuneen", "ADL": "Adel", @@ -412,6 +417,7 @@ "AGVC": "AgaveCoin", "AGVE": "Agave", "AGX": "Agricoin", + "AHARWBTC": "Aave Harmony WBTC", "AHOO": "Ahoolee", "AHT": "AhaToken", "AI": "Sleepless", @@ -626,6 +632,7 @@ "ALITA": "Alita Network", "ALITATOKEN": "Alita Token", "ALIX": "AlinX", + "ALK": "Alkemi Network DAO Token", "ALKI": "Alkimi", "ALKIMI": "ALKIMI", "ALLBI": "ALL BEST ICO", @@ -639,6 +646,7 @@ "ALMANAK": "Almanak", "ALMC": "Awkward Look Monkey Club", "ALME": "Alita", + "ALMEELA": "Almeela", "ALMOND": "Almond", "ALN": "Aluna", "ALNV1": "Aluna v1", @@ -690,6 +698,7 @@ "AMAL": "AMAL", "AMAPT": "Amnis Finance", "AMATEN": "Amaten", + "AMATO": "AMATO", "AMAZINGTEAM": "AmazingTeamDAO", "AMB": "AirDAO", "AMBER": "AmberCoin", @@ -825,6 +834,7 @@ "AOK": "AOK", "AOL": "AOL (America Online)", "AOP": "Ark Of Panda", + "AOPTWBTC": "Aave Optimism WBTC", "AOS": "AOS", "AOT": "Age of Tanks", "AP": "America Party", @@ -861,6 +871,7 @@ "APOL": "Apollo FTW", "APOLL": "Apollon Limassol", "APOLLO": "Apollo Crypto", + "APOLWBTC": "Aave Polygon WBTC", "APP": "Moon App", "APPA": "Dappad", "APPC": "AppCoins", @@ -914,6 +925,7 @@ "ARAW": "Araw", "ARB": "Arbitrum", "ARBI": "Arbipad", + "ARBINU": "ArbInu", "ARBIT": "Arbit Coin", "ARBP": "ARB Protocol", "ARBS": "Arbswap", @@ -1041,6 +1053,7 @@ "ASBNB": "Astherus Staked BNB", "ASC": "All InX SMART CHAIN", "ASCEND": "Ascend", + "ASCN": "AlphaScan", "ASD": "AscendEX Token", "ASDEX": "AstraDEX", "ASEED": "aUSD SEED (Acala)", @@ -1133,6 +1146,7 @@ "ATLA": "Atleta Network", "ATLAS": "Star Atlas", "ATLASD": "Atlas DEX", + "ATLASOFUSA": "Atlas", "ATLX": "Atlantis Loans Polygon", "ATM": "Atletico de Madrid Fan Token", "ATMA": "ATMA", @@ -1173,6 +1187,7 @@ "AUCO": "Advanced United Continent", "AUCTION": "Bounce", "AUDC": "Aussie Digital", + "AUDD": "Australian Digital Dollar", "AUDF": "Forte AUD", "AUDIO": "Audius", "AUDM": "Macropod Stablecoin", @@ -1260,6 +1275,7 @@ "AWARE": "ChainAware.ai", "AWARETOKEN": "AWARE", "AWAX": "AWAX", + "AWBTC": "Aave interest bearing WBTC", "AWC": "Atomic Wallet Coin", "AWE": "AWE Network", "AWK": "Awkward Monkey Base", @@ -1302,6 +1318,7 @@ "AXT": "AIX", "AXYS": "Axys", "AYA": "Aryacoin", + "AYFI": "Aave YFI", "AYNI": "Ayni Gold", "AZ": "Azbit", "AZA": "Kaliza", @@ -1314,6 +1331,7 @@ "AZR": "Azure", "AZU": "Azultec", "AZUKI": "Azuki", + "AZUKI2": "AZUKI 2.0", "AZUKIDAO": "AzukiDAO", "AZUM": "Azuma Coin", "AZUR": "Azuro Protocol", @@ -1535,6 +1553,7 @@ "BAOM": "Battle of Memes", "BAOS": "BaoBaoSol", "BAOV1": "BaoToken v1", + "BAP3X": "bAP3X", "BAR": "FC Barcelona Fan Token", "BARA": "Capybara", "BARAKATUH": "Barakatuh", @@ -1678,6 +1697,7 @@ "BCOINM": "Bomb Crypto (MATIC)", "BCOINSOL": "Bomb Crypto (SOL)", "BCOINTON": "Bomb Crypto (TON)", + "BCONG": "BabyCong", "BCOQ": "BLACK COQINU", "BCP": "BlockChainPeople", "BCPAY": "Bitcashpay", @@ -1735,6 +1755,7 @@ "BEATAI": "eBeat AI", "BEATLES": "JohnLennonC0IN", "BEATS": "Sol Beats", + "BEATSWAP": "BeatSwap", "BEATTOKEN": "BEAT Token", "BEAVER": "beaver", "BEB1M": "BeB", @@ -1851,6 +1872,7 @@ "BFLOKI": "BurnFloki", "BFLY": "Butterfly Protocol", "BFM": "BenefitMine", + "BFR": "Buffer Token", "BFT": "BF Token", "BFTB": "Brazil Fan Token", "BFTC": "BITS FACTOR", @@ -2098,6 +2120,7 @@ "BLACKSALE": "Black Sale", "BLACKST": "Black Stallion", "BLACKSWAN": "BlackSwan AI", + "BLACKWHALE": "The Black Whale", "BLADE": "BladeGames", "BLADEW": "BladeWarrior", "BLAKEBTC": "BlakeBitcoin", @@ -2143,7 +2166,8 @@ "BLOB": "B.O.B the Blob", "BLOBERC20": "Blob", "BLOC": "Blockcloud", - "BLOCK": "Blockasset", + "BLOCK": "Block", + "BLOCKASSET": "Blockasset", "BLOCKB": "Block Browser", "BLOCKBID": "Blockbid", "BLOCKF": "Block Farm Club", @@ -2218,6 +2242,7 @@ "BM": "BitMoon", "BMAGA": "Baby Maga", "BMARS": "Binamars", + "BMAX": "BMAX", "BMB": "Beamable Network Token", "BMBO": "Bamboo Coin", "BMC": "Blackmoon Crypto", @@ -2495,6 +2520,7 @@ "BOWSC": "BowsCoin", "BOWSER": "Bowser", "BOX": "DeBoxToken", + "BOXABL": "BOXABL", "BOXCAT": "BOXCAT", "BOXETH": "Cat-in-a-Box Ether", "BOXT": "BOX Token", @@ -2579,6 +2605,7 @@ "BRETTGOLD": "Brett Gold", "BRETTONETH": "Brett ETH", "BRETTSUI": "Brett (brettsui.com)", + "BREV": "Brevis Token", "BREW": "CafeSwap Token", "BREWERY": "Brewery Consortium Coin", "BREWLABS": "Brewlabs", @@ -2737,6 +2764,7 @@ "BTCAS": "BitcoinAsia", "BTCAT": "Bitcoin Cat", "BTCB": "Bitcoin BEP2", + "BTCBAM": "BitCoin Bam", "BTCBASE": "Bitcoin on Base", "BTCBR": "Bitcoin BR", "BTCBRV1": "Bitcoin BR v1", @@ -3126,7 +3154,8 @@ "CASPER": "Casper DeFi", "CASPERTOKEN": "Casper Token", "CASPUR": "Caspur Zoomies", - "CAST": "Castello Coin", + "CAST": "CAST ORACLES", + "CASTELLOCOIN": "Castello Coin", "CASTLE": "bitCastle", "CAT": "Simon's Cat", "CATA": "CATAMOTO", @@ -3236,6 +3265,7 @@ "CBX": "CropBytes", "CBXRP": "Coinbase Wrapped XRP", "CBY": "Carbify", + "CBYTE": "CBYTE", "CC": "Canton Coin", "CC10": "Cryptocurrency Top 10 Tokens Index", "CCA": "CCA", @@ -3310,6 +3340,7 @@ "CENTRA": "Centra", "CENTS": "Centience", "CENX": "Centcex", + "CEO": "CEO", "CEODOGE": "CEO DOGE", "CERBER": "CERBEROGE", "CERE": "Cere Network", @@ -3695,7 +3726,8 @@ "COC": "Coin of the champions", "COCAINE": "THE GOOD STUFF", "COCK": "Shibacock", - "COCO": "COCO COIN", + "COCO": "coco", + "COCOCOIN": "COCO COIN", "COCONUT": "Coconut", "COCOR": "Cocoro", "COCORO": "Cocoro", @@ -3726,6 +3758,7 @@ "COI": "Coinnec", "COINAI": "Coinbase AI Agent", "COINB": "Coinbidex", + "COINBANK": "CoinBank", "COINBT": "CoinBot", "COINBUCK": "Coinbuck", "COINDEALTOKEN": "CoinDeal Token", @@ -3750,6 +3783,7 @@ "COLA": "Cola", "COLISEUM": "Coliseum", "COLL": "Collateral Pay", + "COLLAB": "Collab.Land", "COLLAR": "PolyPup Finance", "COLLAT": "Collaterize", "COLLE": "Collective Care", @@ -3970,6 +4004,7 @@ "CREPECOIN": "Crepe Coin", "CRES": "Cresio", "CRESV1": "Cresio v1", + "CRETA": "Creta World", "CREV": "CryptoRevolution", "CREVA": "Creva Coin", "CREW": "CREW INU", @@ -4778,8 +4813,10 @@ "DINGER": "Dinger Token", "DINGO": "Dingocoin", "DINNER": "Trump Dinner", - "DINO": "DinoLFG", + "DINO": "DINO", + "DINOLFG": "DinoLFG", "DINOS": "Dinosaur Inu", + "DINOSOL": "DINOSOL", "DINOSWAP": "DinoSwap", "DINT": "DinarTether", "DINU": "Dogey-Inu", @@ -4974,6 +5011,7 @@ "DOGEZILLA": "DogeZilla", "DOGEZILLAV1": "DogeZilla v1", "DOGG": "Doggo", + "DOGGO": "DOGGO", "DOGGS": "Doggensnout", "DOGGY": "Doggy", "DOGGYCOIN": "DOGGY", @@ -5487,6 +5525,7 @@ "EGGC": "EggCoin", "EGGMAN": "Eggman Inu", "EGGP": "Eggplant Finance", + "EGGT": "Egg N Partners", "EGGY": "EGGY", "EGI": "eGame", "EGL": "The Eagle Of Truth", @@ -5711,6 +5750,7 @@ "EPETS": "Etherpets", "EPIC": "Epic Chain", "EPICCASH": "Epic Cash", + "EPICV1": "Ethernity Chain", "EPIK": "EPIK Token", "EPIKO": "Epiko", "EPIX": "Byepix", @@ -6140,6 +6180,7 @@ "FCTC": "FaucetCoin", "FCTR": "FactorDAO", "FDC": "Fidance", + "FDGC": "FINTECH DIGITAL GOLD COIN", "FDLS": "FIDELIS", "FDM": "Fandom", "FDO": "Firdaos", @@ -6440,7 +6481,9 @@ "FOFARIO": "Fofar", "FOFO": "FOFO", "FOFOTOKEN": "FOFO Token", + "FOG": "FOGnet", "FOGE": "Fat Doge", + "FOGV1": "FOGnet v1", "FOIN": "Foin", "FOL": "Folder Protocol", "FOLD": "Manifold Finance", @@ -6709,6 +6752,7 @@ "FWB": "Friends With Benefits Pro", "FWBV1": "Friends With Benefits Pro v1", "FWC": "Qatar 2022", + "FWCL": "Legends", "FWH": "FigureWifHat", "FWOG": "Fwog", "FWT": "Freeway Token", @@ -7042,6 +7086,7 @@ "GIGASWAP": "GigaSwap", "GIGGLE": "Giggle Fund", "GIGGLEACADEMY": "Giggle Academy", + "GIGL": "GIGGLE PANDA", "GIGS": "Climate101", "GIGX": "GigXCoin", "GIKO": "Giko Cat", @@ -7558,6 +7603,7 @@ "HACHIKO": "Hachiko Inu Token", "HACHIONB": "Hachi On Base", "HACK": "HACK", + "HADES": "Hades", "HAEDAL": "Haedal Protocol", "HAGGIS": "New Born Haggis Pygmy Hippo", "HAHA": "Hasaki", @@ -7772,6 +7818,7 @@ "HIENS3": "hiENS3", "HIENS4": "hiENS4", "HIFI": "Hifi Finance", + "HIFIDENZA": "hiFIDENZA", "HIFLUF": "hiFLUF", "HIFRIENDS": "hiFRIENDS", "HIGAZERS": "hiGAZERS", @@ -7811,6 +7858,7 @@ "HITBTC": "HitBTC Token", "HITOP": "Hitop", "HIUNDEAD": "hiUNDEAD", + "HIVALHALLA": "hiVALHALLA", "HIVE": "Hive", "HIVP": "HiveSwap", "HIX": "HELIX Orange", @@ -7984,6 +8032,7 @@ "HSN": "Hyper Speed Network", "HSOL": "Helius Staked SOL", "HSP": "Horse Power", + "HSR": "Hshare", "HSS": "Hashshare", "HST": "Decision Token", "HSUI": "Suicune", @@ -8005,6 +8054,7 @@ "HTN": "Hoosat Network", "HTO": "Heavenland HTO", "HTR": "Hathor", + "HTS": "Home3", "HTT": "Hello Art", "HTX": "HTX", "HTZ": "Hertz Network", @@ -8427,6 +8477,7 @@ "IQN": "IQeon", "IQQ": "Iqoniq", "IQT": "IQ Protocol", + "IR": "Infrared Governance Token", "IRA": "Diligence", "IRC": "IRIS", "IRENA": "Irena Coin Apps", @@ -8558,6 +8609,7 @@ "JAWN": "Long Jawn Silvers", "JAWS": "AutoShark", "JAY": "Jaypeggers", + "JBC": "Japan Brand Coin", "JBO": "JBOX", "JBOT": "JACKBOT", "JBS": "JumBucks Coin", @@ -8596,6 +8648,7 @@ "JERRYINUCOM": "Jerry Inu", "JES": "Jesus", "JESSE": "jesse", + "JESSECOIN": "jesse", "JEST": "Jester", "JESUS": "Jesus Coin", "JET": "Jet Protocol", @@ -8624,6 +8677,7 @@ "JIM": "Jim", "JIN": "JinPeng", "JIND": "JINDO INU", + "JINDO": "JINDOGE", "JINDOGE": "Jindoge", "JIO": "JIO Token", "JITOSOL": "Jito Staked SOL", @@ -8865,6 +8919,7 @@ "KDC": "Klondike Coin", "KDG": "Kingdom Game 4.0", "KDIA": "KDIA COIN", + "KDK": "Kodiak Token", "KDOE": "Kudoe", "KDOGE": "KingDoge", "KDT": "Kenyan Digital Token", @@ -8926,6 +8981,7 @@ "KGC": "Krypton Galaxy Coin", "KGEN": "KGeN", "KGO": "Kiwigo", + "KGST": "KGST", "KGT": "Kaby Gaming Token", "KHAI": "khai", "KHEOWZOO": "khaokheowzoo", @@ -9074,6 +9130,7 @@ "KNUT": "Knut From Zoo", "KNUXX": "Knuxx Bully of ETH", "KNW": "Knowledge", + "KO": "Kyuzo's Friends", "KOAI": "KOI", "KOALA": "KOALA", "KOBAN": "KOBAN", @@ -9089,6 +9146,7 @@ "KOII": "Koii", "KOIN": "Koinos", "KOINB": "KoinBülteni Token", + "KOINDEX": "KOIN", "KOINETWORK": "Koi Network", "KOIP": "KoiPond", "KOJI": "Koji", @@ -9548,8 +9606,9 @@ "LIQUIDIUM": "LIQUIDIUM•TOKEN", "LIR": "Let it Ride", "LIS": "Realis Network", - "LISA": "Lisa Simpson", + "LISA": "LISA Token", "LISAS": "Lisa Simpson", + "LISASIMPSONCLUB": "Lisa Simpson", "LIST": "KList Protocol", "LISTA": "Lista DAO", "LISTEN": "Listen", @@ -9732,6 +9791,7 @@ "LQT": "Lifty", "LQTY": "Liquity", "LRC": "Loopring", + "LRCV1": "Loopring v1", "LRDS": "BLOCKLORDS", "LRG": "Largo Coin", "LRN": "Loopring [NEO]", @@ -9950,6 +10010,7 @@ "MAGICK": "Cosmic Universe Magick", "MAGICV": "Magicverse", "MAGIK": "Magik Finance", + "MAGMA": "MAGMA", "MAGN": "Magnate Finance", "MAGNE": "Magnetix", "MAGNET": "Yield Magnet", @@ -10198,6 +10259,7 @@ "MCU": "MediChain", "MCUSD": "Moola Celo USD", "MCV": "MCV Token", + "MCX": "MachiX Token", "MD": "MetaDeck", "MDA": "Moeda", "MDAI": "MindAI", @@ -10366,6 +10428,7 @@ "METANIAV1": "METANIAGAMES", "METANO": "Metano", "METAPK": "Metapocket", + "METAPLACE": "Metaplace", "METAQ": "MetaQ", "METAS": "Metaseer", "METAT": "MetaTrace", @@ -10510,6 +10573,7 @@ "MIMO": "MIMO Parallel Governance Token", "MIN": "MINDOL", "MINA": "Mina Protocol", + "MINAR": "Miner Arena", "MINC": "MinCoin", "MIND": "Morpheus Labs", "MINDBODY": "Mind Body Soul", @@ -10911,7 +10975,7 @@ "MPAA": "MPAA", "MPAD": "MultiPad", "MPAY": "Menapay", - "MPC": "Metaplace", + "MPC": "Partisia Blockchain", "MPD": "Metapad", "MPG": "Max Property Group", "MPH": "Morpher", @@ -11010,6 +11074,7 @@ "MTHB": "MTHAIBAHT", "MTHD": "Method Finance", "MTHN": "MTH Network", + "MTHT": "MetaHint", "MTIK": "MatikaToken", "MTIX": "Matrix Token", "MTK": "Moya Token", @@ -11175,6 +11240,7 @@ "N3": "Network3", "N3DR": "NeorderDAO ", "N3ON": "N3on", + "N4T": "Nobel For Trump", "N64": "N64", "N7": "Number7", "N8V": "NativeCoin", @@ -11722,6 +11788,7 @@ "NWIF": "neirowifhat", "NWP": "NWPSolution", "NWS": "Nodewaves", + "NXA": "NEXA Agent", "NXC": "Nexium", "NXD": "Nexus Dubai", "NXDT": "NXD Next", @@ -11766,7 +11833,8 @@ "OAS": "Oasis City", "OASC": "Oasis City", "OASI": "Oasis Metaverse", - "OASIS": "Oasis", + "OASIS": "OASIS", + "OASISPLATFORM": "Oasis", "OAT": "OAT Network", "OATH": "OATH Protocol", "OAX": "Oax", @@ -11983,12 +12051,14 @@ "ONUS": "ONUS", "ONX": "OnX.finance", "OOB": "Oobit", + "OOBV1": "Oobit", "OOE": "OpenOcean", "OOFP": "OOFP", "OOGI": "OOGI", "OOKI": "Ooki", "OOKS": "Onooks", "OOM": "OomerBot", + "OOOO": "oooo", "OOPS": "OOPS", "OORC": "Orbit Bridge Klaytn Orbit Chain", "OORT": "OORT", @@ -12838,7 +12908,7 @@ "PMOON": "Pookimoon", "PMPY": "Prometheum Prodigy", "PMR": "Pomerium Utility Token", - "PMT": "POWER MARKET", + "PMT": "Public Masterpiece Token", "PMTN": "Peer Mountain", "PMX": "Phillip Morris xStock", "PNB": "Pink BNB", @@ -13001,7 +13071,9 @@ "POUW": "Pouwifhat", "POW": "PowBlocks", "POWELL": "Jerome Powell", - "POWER": "Powerloom Token", + "POWER": "Power", + "POWERLOOM": "Powerloom Token", + "POWERMARKET": "POWER MARKET", "POWR": "Power Ledger", "POWSCHE": "Powsche", "POX": "Monkey Pox", @@ -13269,6 +13341,7 @@ "PXL": "PIXEL", "PXP": "PointPay", "PXT": "Pixer Eternity", + "PYBOBO": "Capybobo", "PYC": "PayCoin", "PYE": "CreamPYE", "PYI": "PYRIN", @@ -13470,6 +13543,7 @@ "RAIREFLEX": "Rai Reflex Index", "RAISE": "Raise Token", "RAIT": "Rabbitgame", + "RAITOKEN": "RAI", "RAIZER": "RAIZER", "RAK": "Rake Finance", "RAKE": "Rake Coin", @@ -13497,10 +13571,11 @@ "RATOTHERAT": "Rato The Rat", "RATS": "Rats", "RATWIF": "RatWifHat", - "RAVE": "Ravendex", + "RAVE": "RaveDAO", "RAVELOUS": "Ravelous", "RAVEN": "Raven Protocol", "RAVENCOINC": "Ravencoin Classic", + "RAVENDEX": "Ravendex", "RAWDOG": "RawDog", "RAWG": "RAWG", "RAY": "Raydium", @@ -13810,7 +13885,8 @@ "RIVUS": "RivusDAO", "RIYA": "Etheriya", "RIZ": "Rivalz Network", - "RIZE": "Rizespor Token", + "RIZE": "RIZE", + "RIZESPOR": "Rizespor Token", "RIZO": "HahaYes", "RIZOLOL": "Rizo", "RIZZ": "Rizz", @@ -13856,6 +13932,7 @@ "RNT": "REAL NIGGER TATE", "RNTB": "BitRent", "RNX": "ROONEX", + "ROA": "ROA CORE", "ROAD": "ROAD", "ROAM": "Roam Token", "ROAR": "Alpha DEX", @@ -13993,6 +14070,7 @@ "RTM": "Raptoreum", "RTR": "Restore The Republic", "RTT": "Restore Truth Token", + "RTX": "RateX", "RU": "RIFI United", "RUBB": "Rubber Ducky Cult", "RUBCASH": "RUBCASH", @@ -14283,7 +14361,7 @@ "SCOIN": "ShinCoin", "SCONE": "Sportcash One", "SCOOBY": "Scooby coin", - "SCOR": "Scorista", + "SCOR": "Scor", "SCORE": "Scorecoin", "SCOT": "Scotcoin", "SCOTT": "Scottish", @@ -14377,6 +14455,7 @@ "SELFIEC": "Selfie Cat", "SELFT": "SelfToken", "SELLC": "Sell Token", + "SELO": "SELO+", "SEM": "Semux", "SEN": "Sentaro", "SENA": "Ethena Staked ENA", @@ -14482,7 +14561,6 @@ "SHANG": "Shanghai Inu", "SHAR": "Shark Cat", "SHARBI": "SHARBI", - "SHARD": "ShardCoin", "SHARDS": "WorldShards", "SHARE": "Seigniorage Shares", "SHARECHAIN": "ShareChain", @@ -14580,6 +14658,7 @@ "SHIRO": "Shiro Neko", "SHIROSOL": "Shiro Neko (shirosol.online)", "SHIRYOINU": "Shiryo-Inu", + "SHISA": "SHISA", "SHISHA": "Shisha Coin", "SHIT": "I will poop it NFT", "SHITC": "Shitcoin", @@ -14628,9 +14707,11 @@ "SHUFFLE": "SHUFFLE!", "SHVR": "Shivers", "SHX": "Stronghold Token", + "SHXV1": "Stronghold Token v1", "SHY": "Shytoshi Kusama", "SHYTCOIN": "ShytCoin", "SI": "Siren", + "SI14": "Si14", "SIACLASSIC": "SiaClassic", "SIB": "SibCoin", "SIBA": "SibaInu", @@ -14831,6 +14912,7 @@ "SMARTLOX": "SmartLOX", "SMARTM": "SmartMesh", "SMARTMEME": "SmartMEME", + "SMARTMFG": "Smart MFG", "SMARTNFT": "SmartNFT", "SMARTO": "smARTOFGIVING", "SMARTSHARE": "Smartshare", @@ -15040,6 +15122,7 @@ "SOLNAV": "SOLNAV AI", "SOLNIC": "Solnic", "SOLO": "Sologenic", + "SOLOM": "Solomon", "SOLOR": "Solordi", "SOLP": "SolPets", "SOLPAD": "Solpad Finance", @@ -15332,6 +15415,7 @@ "STACS": "STACS Token", "STAFIRETH": "StaFi Staked ETH", "STAGE": "Stage", + "STAI": "StereoAI", "STAK": "Jigstack", "STAKE": "xDai Chain", "STAKEDETH": "StakeHound Staked Ether", @@ -15422,6 +15506,7 @@ "STIMA": "STIMA", "STING": "Sting", "STINJ": "Stride Staked INJ", + "STIPS": "Stips", "STITCH": "Stitch", "STIX": "STIX", "STJUNO": "Stride Staked JUNO", @@ -15597,6 +15682,7 @@ "SUPERBONK": "SUPER BONK", "SUPERC": "SuperCoin", "SUPERCAT": "SUPERCAT", + "SUPERCYCLE": "Crypto SuperCycle", "SUPERDAPP": "SuperDapp", "SUPERF": "SUPER FLOKI", "SUPERGROK": "SuperGrok", @@ -15855,6 +15941,7 @@ "TBILL": "OpenEden T-Bills", "TBILLV1": "OpenEden T-Bills v1", "TBIS": "TBIS token", + "TBK": "TBK Token", "TBL": "Tombola", "TBLLX": "TBLL xStock", "TBR": "Tuebor", @@ -15902,7 +15989,8 @@ "TDROP": "ThetaDrop", "TDS": "TokenDesk", "TDX": "Tidex Token", - "TEA": "TeaDAO", + "TEA": "TeaFi Token", + "TEADAO": "TeaDAO", "TEAM": "TeamUP", "TEARS": "Liberals Tears", "TEC": "TeCoin", @@ -15933,7 +16021,7 @@ "TEMM": "TEM MARKET", "TEMP": "Tempus", "TEMPLE": "TempleDAO", - "TEN": "Tokenomy", + "TEN": "TEN", "TEND": "Tendies", "TENDIE": "TendieSwap", "TENET": "TENET", @@ -15947,8 +16035,8 @@ "TEQ": "Teq Network", "TER": "TerraNovaCoin", "TERA": "TERA", + "TERA2": "Terareum", "TERADYNE": "Teradyne", - "TERAR": "Terareum", "TERAV1": "Terareum v1", "TERAWATT": "Terawatt", "TERM": "Terminal of Simpson", @@ -15975,6 +16063,7 @@ "TETSUO": "Tetsuo Coin", "TETU": "TETU", "TEVA": "Tevaera", + "TEVI": "TEVI Coin", "TEW": "Trump in a memes world", "TEX": "Terrax", "TF47": "Trump Force 47", @@ -15988,7 +16077,7 @@ "TFT": "The Famous Token", "TFUEL": "Theta Fuel", "TGAME": "TrueGame", - "TGC": "TigerCoin", + "TGC": "TG.Casino", "TGCC": "TheGCCcoin", "TGPT": "Trading GPT", "TGRAM": "TG20 TGram", @@ -16026,6 +16115,7 @@ "THEOS": "Theos", "THEP": "The Protocol", "THEPLAY": "PLAY", + "THEREALCHAIN": "REAL", "THERESAMAY": "Theresa May Coin", "THES": "The Standard Protocol (USDS)", "THESTANDARD": "Standard Token", @@ -16051,6 +16141,7 @@ "THOR": "THORSwap", "THOREUM": "Thoreum V3", "THP": "TurboHigh Performance", + "THQ": "Theoriq Token", "THR": "Thorecoin", "THREE": "Three Protocol Token ", "THRT": "ThriveToken", @@ -16078,6 +16169,7 @@ "TIG": "Tigereum", "TIGER": "TIGER", "TIGERC": "TigerCash", + "TIGERCOIN": "TigerCoin", "TIGERCV1": "TigerCash v1", "TIGERMOON": "TigerMoon", "TIGERSHARK": "Tiger Shark", @@ -16154,6 +16246,7 @@ "TME": "Timereum", "TMED": "MDsquare", "TMFT": "Turkish Motorcycle Federation", + "TMG": "T-mac DAO", "TMN": "TranslateMe", "TMNG": "TMN Global", "TMNT": "TMNT", @@ -16193,6 +16286,7 @@ "TOKC": "Tokyo Coin", "TOKE": "Tokemak", "TOKEN": "TokenFi", + "TOKENOMY": "Tokenomy", "TOKENPLACE": "Tokenplace", "TOKENSTARS": "TokenStars", "TOKERO": "TOKERO LevelUP Token", @@ -16312,6 +16406,7 @@ "TRADE": "Polytrade", "TRADEBOT": "TradeBot", "TRADECHAIN": "Trade Chain", + "TRADETIDE": "Trade Tide Token", "TRADEX": "TradeX AI", "TRADOOR": "Tradoor", "TRAI": "Trackgood AI", @@ -16361,6 +16456,7 @@ "TRIAS": "Trias", "TRIBE": "Tribe", "TRIBETOKEN": "TribeToken", + "TRIBEX": "Tribe Token", "TRIBL": "Tribal Token", "TRICK": "TrickyCoin", "TRICKLE": "Trickle", @@ -16882,6 +16978,7 @@ "USDI": "Interest Protocol USDi", "USDJ": "USDJ", "USDK": "USDK", + "USDKG": "USDKG", "USDL": "Lift Dollar", "USDM": "Mountain Protocol", "USDMA": "USD mars", @@ -16946,6 +17043,7 @@ "UT": "Ulord", "UTBAI": "UTB.ai", "UTC": "UltraCoin", + "UTED": "United", "UTG": "UltronGlow", "UTH": "Uther", "UTHR": "Utherverse Xaeon", @@ -16998,6 +17096,7 @@ "VALU": "Value", "VALUE": "Value Liquidity", "VALYR": "Valyr", + "VAM": "Vitalum", "VAMPIRE": "Vampire Inu", "VAN": "Vanspor Token", "VANA": "Vana", @@ -17206,6 +17305,7 @@ "VIZ": "VIZ Token", "VIZION": "ViZion Protocol", "VIZSLASWAP": "VizslaSwap", + "VK": "VK Token", "VKNF": "VKENAF", "VLC": "Volcano Uni", "VLDY": "Validity", @@ -17263,6 +17363,7 @@ "VON": "Vameon", "VONE": "Vone", "VONSPEED": "Andrea Von Speed", + "VOOI": "VOOI", "VOOT": "VootCoin", "VOOZ": "Vooz Coin", "VOPO": "VOPO", @@ -17443,6 +17544,7 @@ "WATCH": "Yieldwatch", "WATER": "Waterfall", "WATERCOIN": "WATER", + "WATLAS": "Wrapped Star Atlas (Portal Bridge)", "WATT": "WATTTON", "WAVAX": "Wrapped AVAX", "WAVES": "Waves", @@ -17524,6 +17626,7 @@ "WEBSIM": "The Css God by Virtuals", "WEBSS": "Websser", "WEC": "Whole Earth Coin", + "WECAN": "Wecan Group", "WECO": "WECOIN", "WED": "Wednesday Inu", "WEEBS": "Weebs", @@ -17561,8 +17664,9 @@ "WEPC": "World Earn & Play Community", "WEPE": "Wall Street Pepe", "WERK": "Werk Family", + "WESHOWTOKEN": "WeShow Token", "WEST": "Waves Enterprise", - "WET": "WeShow Token", + "WET": "HumidiFi Token", "WETH": "WETH", "WETHV1": "WETH v1", "WETHW": "Wrapped EthereumPoW", @@ -17617,6 +17721,7 @@ "WHISKEY": "WHISKEY", "WHITE": "WhiteRock", "WHITEHEART": "Whiteheart", + "WHITEWHALE": "The White Whale", "WHL": "WhaleCoin", "WHO": "Truwho", "WHOLE": "Whole Network", @@ -17827,6 +17932,7 @@ "WPP": "Green Energy Token", "WPR": "WePower", "WQT": "Work Quest", + "WR": "White Rat", "WRC": "Worldcore", "WREACT": "Wrapped REACT", "WRK": "BlockWRK", @@ -18005,6 +18111,7 @@ "XCHF": "CryptoFranc", "XCHNG": "Chainge Finance", "XCI": "Cannabis Industry Coin", + "XCL": "Xcellar", "XCLR": "ClearCoin", "XCM": "CoinMetro", "XCN": "Onyxcoin", @@ -18043,7 +18150,8 @@ "XEC": "eCash", "XED": "Exeedme", "XEDO": "XedoAI", - "XEL": "Xel", + "XEL": "XELIS", + "XELCOIN": "Xel", "XELS": "XELS Coin", "XEM": "NEM", "XEN": "XEN Crypto", @@ -18361,13 +18469,15 @@ "YEAI": "YE AI Agent", "YEARN": "YearnTogether", "YEC": "Ycash", - "YEE": "Yeeco", + "YEE": "Yee Token", + "YEECO": "Yeeco", "YEED": "Yggdrash", "YEEHAW": "YEEHAW", "YEET": "Yeet", "YEETI": "YEETI 液体", "YEFI": "YeFi", "YEL": "Yel.Finance", + "YELLOWWHALE": "The Yellow Whale", "YELP": "Yelpro", "YEON": "Yeon", "YEPE": "Yellow Pepe", From d58e5f73e9162a589283da8c729bd5813f818bb9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:56:19 +0100 Subject: [PATCH 152/157] Bugfix/fix case-insensitive sorting in accounts table (#6178) * Fix case-insensitive sorting by account name * Update changelog --- CHANGELOG.md | 1 + libs/common/src/lib/helper.ts | 11 +++++++++++ .../lib/accounts-table/accounts-table.component.ts | 6 +++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b67aeea4a..b3fdc21fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the filtering by asset class in the endpoint `GET api/v1/portfolio/holdings` +- Fixed the case-insensitive sorting in the accounts table component ## 2.228.0 - 2026-01-03 diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 7452b604c..9c1a0f104 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -12,6 +12,7 @@ import { subDays } from 'date-fns'; import { ca, de, es, fr, it, nl, pl, pt, tr, uk, zhCN } from 'date-fns/locale'; +import { get, isNil, isString } from 'lodash'; import { DEFAULT_CURRENCY, @@ -242,6 +243,16 @@ export function getLocale() { return navigator.language ?? locale; } +export function getLowercase(object: object, path: string) { + const value = get(object, path); + + if (isNil(value)) { + return ''; + } + + return isString(value) ? value.toLocaleLowerCase() : value; +} + export function getNumberFormatDecimal(aLocale?: string) { const formatObject = new Intl.NumberFormat(aLocale).formatToParts(9999.99); diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index 699de6d7e..fe91e1eda 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -1,5 +1,5 @@ import { ConfirmationDialogType } from '@ghostfolio/common/enums'; -import { getLocale } from '@ghostfolio/common/helper'; +import { getLocale, getLowercase } from '@ghostfolio/common/helper'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -32,7 +32,6 @@ import { trashOutline, walletOutline } from 'ionicons/icons'; -import { get } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, Subscription } from 'rxjs'; @@ -133,8 +132,9 @@ export class GfAccountsTableComponent implements OnChanges, OnDestroy { this.isLoading = true; this.dataSource = new MatTableDataSource(this.accounts); + this.dataSource.sortingDataAccessor = getLowercase; + this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = get; if (this.accounts) { this.isLoading = false; From a84eb7ba564f28d056958a5c7eeed632e1181438 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 11 Jan 2026 14:04:06 +0100 Subject: [PATCH 153/157] Bugfix/fix case-insensitive sorting in benchmark component (#6181) * Fix case-insensitive sorting by name * Update changelog --- CHANGELOG.md | 1 + libs/ui/src/lib/benchmark/benchmark.component.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3fdc21fb..b23f88eff 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 - Fixed the filtering by asset class in the endpoint `GET api/v1/portfolio/holdings` - Fixed the case-insensitive sorting in the accounts table component +- Fixed the case-insensitive sorting in the benchmark component ## 2.228.0 - 2026-01-03 diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index fe53240ed..adef1f41b 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,5 +1,9 @@ import { ConfirmationDialogType } from '@ghostfolio/common/enums'; -import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper'; +import { + getLocale, + getLowercase, + resolveMarketCondition +} from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, Benchmark, @@ -28,7 +32,7 @@ import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; -import { get, isNumber } from 'lodash'; +import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, takeUntil } from 'rxjs'; @@ -111,8 +115,9 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { public ngOnChanges() { if (this.benchmarks) { this.dataSource.data = this.benchmarks; + this.dataSource.sortingDataAccessor = getLowercase; + this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = get; this.isLoading = false; } From 60a64b768df7e7c7a0b172aa8d9427e6200d5df8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:47:50 +0100 Subject: [PATCH 154/157] Bugfix/fix case-insensitive sorting in holdings table component (#6183) * Fix case-insensitive sorting by name * Update changelog --- CHANGELOG.md | 1 + .../src/lib/holdings-table/holdings-table.component.html | 7 +------ libs/ui/src/lib/holdings-table/holdings-table.component.ts | 4 +++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b23f88eff..5c1fad4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the filtering by asset class in the endpoint `GET api/v1/portfolio/holdings` - Fixed the case-insensitive sorting in the accounts table component - Fixed the case-insensitive sorting in the benchmark component +- Fixed the case-insensitive sorting in the holdings table component ## 2.228.0 - 2026-01-03 diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index d3afe9de9..7c7f6b829 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -19,12 +19,7 @@ - + Name diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 1c46e18db..f408f7d9b 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -1,4 +1,4 @@ -import { getLocale } from '@ghostfolio/common/helper'; +import { getLocale, getLowercase } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, PortfolioPosition @@ -92,6 +92,8 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy { this.dataSource = new MatTableDataSource(this.holdings); this.dataSource.paginator = this.paginator; + this.dataSource.sortingDataAccessor = getLowercase; + this.dataSource.sort = this.sort; if (this.holdings) { From 645e8ee30352c3c0825a67a2cd98f4cac93c5c55 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 11 Jan 2026 23:00:48 +0700 Subject: [PATCH 155/157] Bugfix/prevent double counting of cash in net worth (#6171) * Prevent double counting of cash in net worth * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/order/order.service.ts | 49 +++--- .../calculator/portfolio-calculator.ts | 33 ++-- .../roai/portfolio-calculator-cash.spec.ts | 148 ++++++++---------- .../calculator/roai/portfolio-calculator.ts | 6 +- .../exchange-rate-data.service.mock.ts | 6 +- .../portfolio-snapshot.processor.ts | 3 +- .../src/lib/models/timeline-position.ts | 2 + 8 files changed, 127 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1fad4db..74dcfa882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed the net worth calculation to prevent the double counting of cash positions - Fixed the filtering by asset class in the endpoint `GET api/v1/portfolio/holdings` - Fixed the case-insensitive sorting in the accounts table component - Fixed the case-insensitive sorting in the benchmark component diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index a939cb476..9a4f1e46b 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -743,47 +743,50 @@ export class OrderService { /** * Retrieves all orders required for the portfolio calculator, including both standard asset orders - * and synthetic orders representing cash activities. - * - * @param filters - Optional filters to apply to the orders. - * @param userCurrency - The base currency of the user. - * @param userId - The ID of the user. - * @returns An object containing the combined list of activities and the total count. + * and optional synthetic orders representing cash activities. */ @LogPerformance public async getOrdersForPortfolioCalculator({ filters, userCurrency, - userId + userId, + withCash = false }: { + /** Optional filters to apply to the orders. */ filters?: Filter[]; + /** The base currency of the user. */ userCurrency: string; + /** The ID of the user. */ userId: string; + /** Whether to include cash activities in the result. */ + withCash?: boolean; }) { - const nonCashOrders = await this.getOrders({ + const orders = await this.getOrders({ filters, userCurrency, userId, withExcludedAccountsAndActivities: false // TODO }); - const cashDetails = await this.accountService.getCashDetails({ - filters, - userId, - currency: userCurrency - }); + if (withCash) { + const cashDetails = await this.accountService.getCashDetails({ + filters, + userId, + currency: userCurrency + }); - const cashOrders = await this.getCashOrders({ - cashDetails, - filters, - userCurrency, - userId - }); + const cashOrders = await this.getCashOrders({ + cashDetails, + filters, + userCurrency, + userId + }); - return { - activities: [...nonCashOrders.activities, ...cashOrders.activities], - count: nonCashOrders.count + cashOrders.count - }; + orders.activities.push(...cashOrders.activities); + orders.count += cashOrders.count; + } + + return orders; } public async getStatisticsByCurrency( diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index d2b3c0625..8f6cb0efc 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -39,6 +39,7 @@ import { GroupBy } from '@ghostfolio/common/types'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Logger } from '@nestjs/common'; +import { AssetSubClass } from '@prisma/client'; import { Big } from 'big.js'; import { plainToClass } from 'class-transformer'; import { @@ -389,27 +390,33 @@ export abstract class PortfolioCalculator { hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; - valuesBySymbol[item.symbol] = { - currentValues, - currentValuesWithCurrencyEffect, - investmentValuesAccumulated, - investmentValuesAccumulatedWithCurrencyEffect, - investmentValuesWithCurrencyEffect, - netPerformanceValues, - netPerformanceValuesWithCurrencyEffect, - timeWeightedInvestmentValues, - timeWeightedInvestmentValuesWithCurrencyEffect - }; + const includeInTotalAssetValue = + item.assetSubClass !== AssetSubClass.CASH; + + if (includeInTotalAssetValue) { + valuesBySymbol[item.symbol] = { + currentValues, + currentValuesWithCurrencyEffect, + investmentValuesAccumulated, + investmentValuesAccumulatedWithCurrencyEffect, + investmentValuesWithCurrencyEffect, + netPerformanceValues, + netPerformanceValuesWithCurrencyEffect, + timeWeightedInvestmentValues, + timeWeightedInvestmentValuesWithCurrencyEffect + }; + } positions.push({ feeInBaseCurrency, + includeInTotalAssetValue, timeWeightedInvestment, timeWeightedInvestmentWithCurrencyEffect, - dividend: totalDividend, - dividendInBaseCurrency: totalDividendInBaseCurrency, averagePrice: item.averagePrice, currency: item.currency, dataSource: item.dataSource, + dividend: totalDividend, + dividendInBaseCurrency: totalDividendInBaseCurrency, fee: item.fee, firstBuyDate: item.firstBuyDate, grossPerformance: !hasErrors ? (grossPerformance ?? null) : null, diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index e27bb4daa..f5a4ca634 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -14,7 +14,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; -import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; +import { TimelinePosition } from '@ghostfolio/common/models'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { DataSource } from '@prisma/client'; @@ -191,7 +191,8 @@ describe('PortfolioCalculator', () => { const { activities } = await orderService.getOrdersForPortfolioCalculator( { userCurrency: 'CHF', - userId: userDummyData.id + userId: userDummyData.id, + withCash: true } ); @@ -201,7 +202,14 @@ describe('PortfolioCalculator', () => { values: [] }); + const accountBalanceItems = + await accountBalanceService.getAccountBalanceItems({ + userCurrency: 'CHF', + userId: userDummyData.id + }); + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + accountBalanceItems, activities, calculationType: PerformanceCalculationType.ROAI, currency: 'CHF', @@ -210,94 +218,72 @@ describe('PortfolioCalculator', () => { const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); - const historicalData20231231 = portfolioSnapshot.historicalData.find( - ({ date }) => { - return date === '2023-12-31'; - } - ); - const historicalData20240101 = portfolioSnapshot.historicalData.find( - ({ date }) => { - return date === '2024-01-01'; - } - ); - const historicalData20241231 = portfolioSnapshot.historicalData.find( - ({ date }) => { - return date === '2024-12-31'; - } - ); - - /** - * Investment value with currency effect: 1000 USD * 0.85 = 850 CHF - * Total investment: 1000 USD * 0.91 = 910 CHF - * Value (current): 1000 USD * 0.91 = 910 CHF - * Value with currency effect: 1000 USD * 0.85 = 850 CHF - */ - expect(historicalData20231231).toMatchObject({ - date: '2023-12-31', - investmentValueWithCurrencyEffect: 850, - netPerformance: 0, - netPerformanceInPercentage: 0, - netPerformanceInPercentageWithCurrencyEffect: 0, - netPerformanceWithCurrencyEffect: 0, - netWorth: 850, - totalAccountBalance: 0, - totalInvestment: 910, - totalInvestmentValueWithCurrencyEffect: 850, - value: 910, - valueWithCurrencyEffect: 850 - }); - - /** - * Net performance with currency effect: (1000 * 0.86) - (1000 * 0.85) = 10 CHF - * Total investment: 1000 USD * 0.91 = 910 CHF - * Total investment value with currency effect: 1000 USD * 0.85 = 850 CHF - * Value (current): 1000 USD * 0.91 = 910 CHF - * Value with currency effect: 1000 USD * 0.86 = 860 CHF - */ - expect(historicalData20240101).toMatchObject({ - date: '2024-01-01', - investmentValueWithCurrencyEffect: 0, - netPerformance: 0, - netPerformanceInPercentage: 0, - netPerformanceInPercentageWithCurrencyEffect: 0.011764705882352941, - netPerformanceWithCurrencyEffect: 10, - netWorth: 860, - totalAccountBalance: 0, - totalInvestment: 910, - totalInvestmentValueWithCurrencyEffect: 850, - value: 910, - valueWithCurrencyEffect: 860 + const position = portfolioSnapshot.positions.find(({ symbol }) => { + return symbol === 'USD'; }); /** - * Investment value with currency effect: 1000 USD * 0.90 = 900 CHF + * Investment: 2000 USD * 0.91 = 1820 CHF + * Investment value with currency effect: (1000 USD * 0.85) + (1000 USD * 0.90) = 1750 CHF * Net performance: (1000 USD * 1.0) - (1000 USD * 1.0) = 0 CHF - * Net performance with currency effect: (1000 USD * 0.9) - (1000 USD * 0.85) = 50 CHF - * Total investment: 2000 USD * 0.91 = 1820 CHF - * Total investment value with currency effect: (1000 USD * 0.85) + (1000 USD * 0.90) = 1750 CHF - * Value (current): 2000 USD * 0.91 = 1820 CHF - * Value with currency effect: 2000 USD * 0.9 = 1800 CHF + * Total account balance: 2000 USD * 0.85 = 1700 CHF (using the exchange rate on 2024-12-31) + * Value in base currency: 2000 USD * 0.91 = 1820 CHF */ - expect(historicalData20241231).toMatchObject({ - date: '2024-12-31', - investmentValueWithCurrencyEffect: 900, - netPerformance: 0, - netPerformanceInPercentage: 0, - netPerformanceInPercentageWithCurrencyEffect: 0.058823529411764705, - netPerformanceWithCurrencyEffect: 50, - netWorth: 1800, - totalAccountBalance: 0, - totalInvestment: 1820, - totalInvestmentValueWithCurrencyEffect: 1750, - value: 1820, - valueWithCurrencyEffect: 1800 + expect(position).toMatchObject({ + averagePrice: new Big(1), + currency: 'USD', + dataSource: DataSource.YAHOO, + dividend: new Big(0), + dividendInBaseCurrency: new Big(0), + fee: new Big(0), + feeInBaseCurrency: new Big(0), + firstBuyDate: '2023-12-31', + grossPerformance: new Big(0), + grossPerformancePercentage: new Big(0), + grossPerformancePercentageWithCurrencyEffect: new Big( + '0.08211603004634809014' + ), + grossPerformanceWithCurrencyEffect: new Big(70), + includeInTotalAssetValue: false, + investment: new Big(1820), + investmentWithCurrencyEffect: new Big(1750), + marketPrice: null, + marketPriceInBaseCurrency: 0.91, + netPerformance: new Big(0), + netPerformancePercentage: new Big(0), + netPerformancePercentageWithCurrencyEffectMap: { + '1d': new Big('0.01111111111111111111'), + '1y': new Big('0.06937181021989792704'), + '5y': new Big('0.0818817546090273363'), + max: new Big('0.0818817546090273363'), + mtd: new Big('0.01111111111111111111'), + wtd: new Big('-0.05517241379310344828'), + ytd: new Big('0.01111111111111111111') + }, + netPerformanceWithCurrencyEffectMap: { + '1d': new Big(20), + '1y': new Big(60), + '5y': new Big(70), + max: new Big(70), + mtd: new Big(20), + wtd: new Big(-80), + ytd: new Big(20) + }, + quantity: new Big(2000), + symbol: 'USD', + timeWeightedInvestment: new Big('912.47956403269754768392'), + timeWeightedInvestmentWithCurrencyEffect: new Big( + '852.45231607629427792916' + ), + transactionCount: 2, + valueInBaseCurrency: new Big(1820) }); expect(portfolioSnapshot).toMatchObject({ hasErrors: false, - totalFeesWithCurrencyEffect: new Big('0'), - totalInterestWithCurrencyEffect: new Big('0'), - totalLiabilitiesWithCurrencyEffect: new Big('0') + totalFeesWithCurrencyEffect: new Big(0), + totalInterestWithCurrencyEffect: new Big(0), + totalLiabilitiesWithCurrencyEffect: new Big(0) }); }); }); 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 070d7543b..2ceed015d 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -34,7 +34,11 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { let totalTimeWeightedInvestment = new Big(0); let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0); - for (const currentPosition of positions) { + for (const currentPosition of positions.filter( + ({ includeInTotalAssetValue }) => { + return includeInTotalAssetValue; + } + )) { if (currentPosition.feeInBaseCurrency) { totalFeesWithCurrencyEffect = totalFeesWithCurrencyEffect.plus( currentPosition.feeInBaseCurrency diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts index 857c1b5a5..742be36b4 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts @@ -1,5 +1,7 @@ -export const ExchangeRateDataServiceMock = { - getExchangeRatesByCurrency: ({ targetCurrency }): Promise => { +import { ExchangeRateDataService } from './exchange-rate-data.service'; + +export const ExchangeRateDataServiceMock: Partial = { + getExchangeRatesByCurrency: ({ targetCurrency }) => { if (targetCurrency === 'CHF') { return Promise.resolve({ CHFCHF: { diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts index 75a3a8631..58a0a8f8a 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts @@ -50,7 +50,8 @@ export class PortfolioSnapshotProcessor { await this.orderService.getOrdersForPortfolioCalculator({ filters: job.data.filters, userCurrency: job.data.userCurrency, - userId: job.data.userId + userId: job.data.userId, + withCash: true }); const accountBalanceItems = diff --git a/libs/common/src/lib/models/timeline-position.ts b/libs/common/src/lib/models/timeline-position.ts index f683c0951..8eae56cf7 100644 --- a/libs/common/src/lib/models/timeline-position.ts +++ b/libs/common/src/lib/models/timeline-position.ts @@ -50,6 +50,8 @@ export class TimelinePosition { @Type(() => Big) grossPerformanceWithCurrencyEffect: Big; + includeInTotalAssetValue?: boolean; + @Transform(transformToBig, { toClassOnly: true }) @Type(() => Big) investment: Big; From c47a4fdc71d43cb6b29dc12ba602ea47f0456ab1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:02:48 +0100 Subject: [PATCH 156/157] Release 2.229.0 (#6184) --- 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 74dcfa882..4d6e818db 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 +## 2.229.0 - 2026-01-11 ### Changed diff --git a/package-lock.json b/package-lock.json index 9b37e8439..60cf31da5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.228.0", + "version": "2.229.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.228.0", + "version": "2.229.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 140e4ce44..63612d7fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.228.0", + "version": "2.229.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 825a36636790310d4a70f9af06355c1e08ba54b7 Mon Sep 17 00:00:00 2001 From: yaro <43820977+yootaebong@users.noreply.github.com> Date: Tue, 13 Jan 2026 05:48:31 +0900 Subject: [PATCH 157/157] Feature/set up language localization for Korean (ko) (#6136) * Set up language localization for Korean (ko) * Update changelog --- CHANGELOG.md | 6 + apps/client/project.json | 15 + .../components/footer/footer.component.html | 15 +- .../user-account-settings.component.ts | 1 + .../user-account-settings.html | 13 +- .../src/app/pages/features/features-page.html | 5 +- .../product-page.component.ts | 5 +- apps/client/src/locales/messages.ko.xlf | 8665 +++++++++++++++++ libs/common/src/lib/config.ts | 1 + libs/common/src/lib/helper.ts | 17 +- 10 files changed, 8734 insertions(+), 9 deletions(-) create mode 100644 apps/client/src/locales/messages.ko.xlf diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6e818db..a07743c34 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 + +- Set up the language localization for Korean (`ko`) + ## 2.229.0 - 2026-01-11 ### Changed diff --git a/apps/client/project.json b/apps/client/project.json index 09968d23f..38887ca8a 100644 --- a/apps/client/project.json +++ b/apps/client/project.json @@ -26,6 +26,10 @@ "baseHref": "/it/", "translation": "apps/client/src/locales/messages.it.xlf" }, + "ko": { + "baseHref": "/ko/", + "translation": "apps/client/src/locales/messages.ko.xlf" + }, "nl": { "baseHref": "/nl/", "translation": "apps/client/src/locales/messages.nl.xlf" @@ -110,6 +114,10 @@ "baseHref": "/it/", "localize": ["it"] }, + "development-ko": { + "baseHref": "/ko/", + "localize": ["ko"] + }, "development-nl": { "baseHref": "/nl/", "localize": ["nl"] @@ -213,6 +221,9 @@ "sslKey": "apps/client/localhost.pem" }, "configurations": { + "development-ca": { + "buildTarget": "client:build:development-ca" + }, "development-de": { "buildTarget": "client:build:development-de" }, @@ -228,6 +239,9 @@ "development-it": { "buildTarget": "client:build:development-it" }, + "development-ko": { + "buildTarget": "client:build:development-ko" + }, "development-nl": { "buildTarget": "client:build:development-nl" }, @@ -264,6 +278,7 @@ "messages.es.xlf", "messages.fr.xlf", "messages.it.xlf", + "messages.ko.xlf", "messages.nl.xlf", "messages.pl.xlf", "messages.pt.xlf", diff --git a/apps/client/src/app/components/footer/footer.component.html b/apps/client/src/app/components/footer/footer.component.html index 155f27f68..45626620e 100644 --- a/apps/client/src/app/components/footer/footer.component.html +++ b/apps/client/src/app/components/footer/footer.component.html @@ -122,7 +122,9 @@ -->
  • - Chinese + Chinese (简体中文)
  • Deutsch @@ -139,6 +141,13 @@
  • Italiano
  • +
  • Nederlands
  • @@ -153,7 +162,9 @@ 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 c72c2a52d..44be30b9a 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 @@ -89,6 +89,7 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { 'es', 'fr', 'it', + 'ko', 'nl', 'pl', 'pt', diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html index e6ab544c8..93ed614cc 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.html +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -87,7 +87,8 @@ > } Chinese (CommunityChinese / 简体中文 (Community) Italiano (Community) + @if (user?.settings?.isExperimentalFeatures) { + Korean / 한국어 (Community) + } Nederlands (Community) @if (user?.settings?.isExperimentalFeatures) { Українська (CommunityUkrainian / Українська (Community) } diff --git a/apps/client/src/app/pages/features/features-page.html b/apps/client/src/app/pages/features/features-page.html index d172347f7..efe96aec6 100644 --- a/apps/client/src/app/pages/features/features-page.html +++ b/apps/client/src/app/pages/features/features-page.html @@ -260,8 +260,9 @@

    Use Ghostfolio in multiple languages: English, - Chinese, Dutch, French, German, Italian, Polish, Portuguese, - Spanish and Turkish + Chinese, Dutch, French, German, Italian, + + Polish, Portuguese, Spanish and Turkish are currently supported.

    diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts index c8eff35be..14f9554d5 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -43,15 +43,16 @@ export class GfProductPageComponent implements OnInit { isOpenSource: true, key: 'ghostfolio', languages: [ + 'Chinese (简体中文)', 'Deutsch', 'English', 'Español', 'Français', 'Italiano', + 'Korean (한국어)', 'Nederlands', 'Português', - 'Türkçe', - '简体中文' + 'Türkçe' ], name: 'Ghostfolio', origin: $localize`Switzerland`, diff --git a/apps/client/src/locales/messages.ko.xlf b/apps/client/src/locales/messages.ko.xlf new file mode 100644 index 000000000..0e0492f24 --- /dev/null +++ b/apps/client/src/locales/messages.ko.xlf @@ -0,0 +1,8665 @@ + + + + + + about + about + kebab-case + + libs/common/src/lib/routes/routes.ts + 176 + + + libs/common/src/lib/routes/routes.ts + 177 + + + libs/common/src/lib/routes/routes.ts + 182 + + + libs/common/src/lib/routes/routes.ts + 190 + + + libs/common/src/lib/routes/routes.ts + 198 + + + libs/common/src/lib/routes/routes.ts + 206 + + + libs/common/src/lib/routes/routes.ts + 214 + + + + faq + faq + kebab-case + + libs/common/src/lib/routes/routes.ts + 234 + + + libs/common/src/lib/routes/routes.ts + 235 + + + libs/common/src/lib/routes/routes.ts + 239 + + + libs/common/src/lib/routes/routes.ts + 245 + + + + features + features + kebab-case + + libs/common/src/lib/routes/routes.ts + 254 + + + libs/common/src/lib/routes/routes.ts + 255 + + + + license + license + kebab-case + + libs/common/src/lib/routes/routes.ts + 188 + + + libs/common/src/lib/routes/routes.ts + 191 + + + + markets + markets + kebab-case + + libs/common/src/lib/routes/routes.ts + 259 + + + libs/common/src/lib/routes/routes.ts + 260 + + + + pricing + pricing + kebab-case + + libs/common/src/lib/routes/routes.ts + 269 + + + libs/common/src/lib/routes/routes.ts + 270 + + + + privacy-policy + privacy-policy + kebab-case + + libs/common/src/lib/routes/routes.ts + 204 + + + libs/common/src/lib/routes/routes.ts + 207 + + + + register + register + kebab-case + + libs/common/src/lib/routes/routes.ts + 279 + + + libs/common/src/lib/routes/routes.ts + 280 + + + + resources + resources + kebab-case + + libs/common/src/lib/routes/routes.ts + 284 + + + libs/common/src/lib/routes/routes.ts + 285 + + + libs/common/src/lib/routes/routes.ts + 290 + + + libs/common/src/lib/routes/routes.ts + 298 + + + libs/common/src/lib/routes/routes.ts + 306 + + + libs/common/src/lib/routes/routes.ts + 314 + + + libs/common/src/lib/routes/routes.ts + 322 + + + + You are using the Live Demo. + 현재 라이브 데모를 사용 중입니다. + + apps/client/src/app/app.component.html + 12 + + + + Create Account + 계정 생성 + + apps/client/src/app/app.component.html + 13 + + + apps/client/src/app/pages/register/register-page.html + 28 + + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 2 + + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 101 + + + + Frequently Asked Questions (FAQ) + 자주 묻는 질문 + + apps/client/src/app/pages/faq/overview/faq-overview-page.html + 5 + + + apps/client/src/app/pages/faq/saas/saas-page.html + 5 + + + apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html + 5 + + + + The risk of loss in trading can be substantial. It is not advisable to invest money you may need in the short term. + 거래에는 상당한 손실 위험이 따를 수 있습니다. 단기적으로 필요할 수 있는 자금의 투자는 권장되지 않습니다. + + apps/client/src/app/components/footer/footer.component.html + 171 + + + + Alias + 별칭 + + apps/client/src/app/components/access-table/access-table.component.html + 4 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 17 + + + + Grantee + 권한 수신자 + + apps/client/src/app/components/access-table/access-table.component.html + 11 + + + + please + 부탁드립니다 + + apps/client/src/app/pages/pricing/pricing-page.html + 336 + + + + Type + 유형 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 48 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 28 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 15 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 163 + + + + Details + 상세 + + apps/client/src/app/components/access-table/access-table.component.html + 33 + + + + Revoke + 회수 + + apps/client/src/app/components/access-table/access-table.component.html + 96 + + + + with + 와(과) + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 87 + + + + plus + 추가로 + + apps/client/src/app/pages/pricing/pricing-page.html + 202 + + + + Do you really want to revoke this granted access? + 이 부여된 접근 권한을 정말로 회수하시겠습니까? + + apps/client/src/app/components/access-table/access-table.component.ts + 115 + + + + Cash Balance + 현금 잔액 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 45 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 34 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 136 + + + + Platform + 플랫폼 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 90 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 48 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 86 + + + + Cash Balances + 현금 잔액 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 148 + + + + Transfer Cash Balance + 현금 잔액 이체 + + apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.html + 7 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 10 + + + + Name + 이름 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 88 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 311 + + + apps/client/src/app/components/admin-platform/admin-platform.component.html + 22 + + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 15 + + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 46 + + + apps/client/src/app/components/admin-tag/admin-tag.component.html + 22 + + + apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.html + 15 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 15 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 139 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 43 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 137 + + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 28 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 + + + + Total + 합계 + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 55 + + + + Currency + 통화 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 201 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 318 + + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 45 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 25 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 145 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 65 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 283 + + + + Value + 평가액 + + apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.html + 53 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 205 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 208 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 211 + + + libs/ui/src/lib/account-balances/account-balances.component.html + 34 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 171 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 206 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 264 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 300 + + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 98 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 + + + + Edit + 편집 + + apps/client/src/app/components/access-table/access-table.component.html + 76 + + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 267 + + + apps/client/src/app/components/admin-platform/admin-platform.component.html + 74 + + + apps/client/src/app/components/admin-tag/admin-tag.component.html + 67 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 313 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 457 + + + + Delete + 삭제 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 289 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 86 + + + apps/client/src/app/components/admin-overview/admin-overview.html + 131 + + + apps/client/src/app/components/admin-platform/admin-platform.component.html + 85 + + + apps/client/src/app/components/admin-tag/admin-tag.component.html + 78 + + + libs/ui/src/lib/account-balances/account-balances.component.html + 80 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 324 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 484 + + + libs/ui/src/lib/benchmark/benchmark.component.html + 176 + + + + Do you really want to delete this account? + 이 계정을 정말 삭제하시겠습니까? + + libs/ui/src/lib/accounts-table/accounts-table.component.ts + 150 + + + + Asset Profile + 자산 프로필 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 52 + + + + Historical Market Data + 과거 시장 데이터 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 54 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 574 + + + + Data Source + 데이터 소스 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 82 + + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 105 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 179 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 155 + + + libs/ui/src/lib/i18n.ts + 15 + + + + Attempts + 시도 횟수 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 120 + + + + Created + 생성됨 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 134 + + + + Finished + 완료됨 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 143 + + + + Status + 상태 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 152 + + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 92 + + + + and is driven by the efforts of its contributors + 기여자들의 노력으로 발전하고 있습니다 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 49 + + + + Delete Jobs + 작업 삭제 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 193 + + + + View Data + 데이터 보기 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 208 + + + + View Stacktrace + 스택트레이스 보기 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 216 + + + + Delete Job + 작업 삭제 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 224 + + + + Details for + 에 대한 세부정보 + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html + 2 + + + + Date + 날짜 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 161 + + + libs/ui/src/lib/account-balances/account-balances.component.html + 12 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 172 + + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html + 6 + + + + Market Price + 시장 가격 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 132 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 113 + + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html + 26 + + + + Currencies + 통화 + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 132 + + + apps/client/src/app/pages/public/public-page.html + 96 + + + + Everything in + 다음 통화 기준으로 모두 표시 + + apps/client/src/app/pages/pricing/pricing-page.html + 199 + + + + ETFs without Countries + 국가 정보 없는 ETF + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 137 + + + + ETFs without Sectors + 섹터 정보 없는 ETF + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 142 + + + + Do you really want to delete this asset profile? + 이 자산 프로필을 정말 삭제하시겠습니까? + + apps/client/src/app/components/admin-market-data/admin-market-data.service.ts + 37 + + + + Filter by... + 다음 기준으로 필터... + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 386 + + + + First Activity + 첫 활동 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 147 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 219 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 219 + + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 50 + + + + Activities Count + 활동 수 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 156 + + + + Historical Data + 과거 데이터 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 165 + + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html + 44 + + + + Sectors Count + 섹터 수 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 174 + + + + Countries Count + 국가 수 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 183 + + + + Gather Recent Historical Market Data + 최근 과거 시장 데이터 수집 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 225 + + + + Gather All Historical Market Data + 전체 과거 시장 데이터 수집 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 230 + + + + Gather Profile Data + 프로필 데이터 수집 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 234 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 66 + + + + Oops! Could not parse historical data. + 이런! 과거 데이터를 파싱할 수 없습니다. + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts + 263 + + + + Refresh + 새로고침 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 21 + + + + Gather Historical Market Data + 과거 시장 데이터 수집 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 34 + + + + Import + 가져오기 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 155 + + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 190 + + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html + 71 + + + + Sector + 섹터 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 264 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 268 + + + + Country + 국가 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 275 + + + apps/client/src/app/components/admin-users/admin-users.html + 60 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 278 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 53 + + + + Sectors + 섹터 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 281 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 522 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 284 + + + apps/client/src/app/pages/public/public-page.html + 114 + + + + Countries + 국가 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 291 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 533 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 296 + + + + Symbol Mapping + 심볼 매핑 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 386 + + + + and we share aggregated key metrics of the platform’s performance + 또한 플랫폼 성과에 대한 집계된 핵심 지표를 공유합니다 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 32 + + + + Scraper Configuration + 스크래퍼 설정 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 411 + + + + Note + 메모 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 558 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 78 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 275 + + + + Add Asset Profile + 자산 프로필 추가 + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 7 + + + + Search + 검색 + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 16 + + + + Add Manually + 수동 추가 + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 18 + + + + Name, symbol or ISIN + 이름, 심볼 또는 ISIN + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 132 + + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 27 + + + apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html + 10 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 124 + + + + Do you really want to delete this coupon? + 이 쿠폰을 정말 삭제하시겠습니까? + + apps/client/src/app/components/admin-overview/admin-overview.component.ts + 194 + + + + Do you really want to delete this system message? + 이 시스템 메시지를 정말 삭제하시겠습니까? + + apps/client/src/app/components/admin-overview/admin-overview.component.ts + 207 + + + + Do you really want to flush the cache? + 정말로 캐시를 플러시하시겠습니까? + + apps/client/src/app/components/admin-overview/admin-overview.component.ts + 231 + + + + Please set your system message: + 시스템 메시지를 설정하십시오: + + apps/client/src/app/components/admin-overview/admin-overview.component.ts + 251 + + + + Version + 버전 + + apps/client/src/app/components/admin-overview/admin-overview.html + 7 + + + + User Count + 사용자 수 + + apps/client/src/app/components/admin-overview/admin-overview.html + 13 + + + + Activity Count + 활동 수 + + apps/client/src/app/components/admin-overview/admin-overview.html + 19 + + + + per User + 사용자당 + + apps/client/src/app/components/admin-overview/admin-overview.html + 28 + + + + Add Currency + 통화 추가 + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 20 + + + + User Signup + 사용자 가입 + + apps/client/src/app/components/admin-overview/admin-overview.html + 34 + + + + Read-only Mode + 읽기 전용 모드 + + apps/client/src/app/components/admin-overview/admin-overview.html + 48 + + + + System Message + 시스템 메시지 + + apps/client/src/app/components/admin-overview/admin-overview.html + 72 + + + + Set Message + 메시지 설정 + + apps/client/src/app/components/admin-overview/admin-overview.html + 94 + + + + Coupons + 쿠폰 + + apps/client/src/app/components/admin-overview/admin-overview.html + 102 + + + + Add + 추가 + + apps/client/src/app/components/admin-overview/admin-overview.html + 176 + + + libs/ui/src/lib/account-balances/account-balances.component.html + 93 + + + + Housekeeping + 유지 관리 + + apps/client/src/app/components/admin-overview/admin-overview.html + 184 + + + + Flush Cache + 캐시 플러시 + + apps/client/src/app/components/admin-overview/admin-overview.html + 200 + + + + Add Platform + 플랫폼 추가 + + apps/client/src/app/components/admin-platform/admin-platform.component.html + 9 + + + + Url + 링크 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 493 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 545 + + + apps/client/src/app/components/admin-platform/admin-platform.component.html + 38 + + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 25 + + + + Do you really want to delete this platform? + 정말로 이 플랫폼을 삭제하시겠습니까? + + apps/client/src/app/components/admin-platform/admin-platform.component.ts + 107 + + + + By + 에 의해 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 139 + + + + Update platform + 플랫폼 업데이트 + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 8 + + + + Current year + 올해 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 204 + + + + Add platform + 플랫폼 추가 + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 10 + + + + Platforms + 플랫폼 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 195 + + + + Tags + 태그 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 201 + + + libs/ui/src/lib/tags-selector/tags-selector.component.html + 4 + + + libs/ui/src/lib/tags-selector/tags-selector.component.html + 16 + + + + Add Tag + 태그 추가 + + apps/client/src/app/components/admin-tag/admin-tag.component.html + 9 + + + + Do you really want to delete this tag? + 이 태그를 정말로 삭제하시겠습니까? + + apps/client/src/app/components/admin-tag/admin-tag.component.ts + 103 + + + + Update tag + 태그 업데이트 + + apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.html + 8 + + + + Add tag + 태그 추가 + + apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.html + 10 + + + + Do you really want to delete this user? + 이 사용자를 정말로 삭제하시겠습니까? + + apps/client/src/app/components/admin-users/admin-users.component.ts + 210 + + + + User + 사용자 + + apps/client/src/app/components/admin-tag/admin-tag.component.html + 31 + + + apps/client/src/app/components/admin-users/admin-users.html + 12 + + + apps/client/src/app/components/header/header.component.html + 231 + + + + No auto-renewal on membership. + 멤버십 자동 갱신은 없습니다. + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 74 + + + + Engagement per Day + 일일 참여 + + apps/client/src/app/components/admin-users/admin-users.html + 140 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 89 + + + + Last Request + 마지막 요청 + + apps/client/src/app/components/admin-users/admin-users.html + 186 + + + + Impersonate User + 사용자 대리 접속 + + apps/client/src/app/components/admin-users/admin-users.html + 233 + + + + Delete User + 사용자 삭제 + + apps/client/src/app/components/admin-users/admin-users.html + 254 + + + + Compare with... + 비교해보세요... + + apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html + 18 + + + + Manage Benchmarks + 벤치마크 관리 + + apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html + 35 + + + + Portfolio + 포트폴리오 + + apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts + 140 + + + apps/client/src/app/components/header/header.component.html + 44 + + + apps/client/src/app/components/header/header.component.html + 258 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 94 + + + libs/common/src/lib/routes/routes.ts + 151 + + + + Benchmark + 기준 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 378 + + + apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts + 152 + + + + Current Market Mood + 현재 시장 분위기 + + apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html + 12 + + + + About Ghostfolio + 고스트폴리오 소개 + + apps/client/src/app/components/header/header.component.html + 326 + + + apps/client/src/app/pages/about/overview/about-overview-page.html + 5 + + + + Sign in + 로그인 + + apps/client/src/app/components/header/header.component.html + 422 + + + apps/client/src/app/components/header/header.component.ts + 297 + + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 79 + + + libs/common/src/lib/routes/routes.ts + 81 + + + libs/common/src/lib/routes/routes.ts + 157 + + + + Oops! Incorrect Security Token. + 이런! 잘못된 보안 토큰. + + apps/client/src/app/components/header/header.component.ts + 312 + + + apps/client/src/app/components/user-account-access/user-account-access.component.ts + 154 + + + apps/client/src/app/components/user-account-settings/user-account-settings.component.ts + 192 + + + + Manage Activities + 활동 관리 + + apps/client/src/app/components/home-holdings/home-holdings.html + 67 + + + + Fear + 두려움 + + apps/client/src/app/components/home-market/home-market.component.ts + 42 + + + apps/client/src/app/components/markets/markets.component.ts + 47 + + + libs/ui/src/lib/i18n.ts + 108 + + + + Greed + 탐욕 + + apps/client/src/app/components/home-market/home-market.component.ts + 43 + + + apps/client/src/app/components/markets/markets.component.ts + 48 + + + libs/ui/src/lib/i18n.ts + 109 + + + + Last Days + 지난 + + apps/client/src/app/components/home-market/home-market.html + 7 + + + apps/client/src/app/components/markets/markets.html + 17 + + + + Welcome to Ghostfolio + Ghostfolio에 오신 것을 환영합니다 + + apps/client/src/app/components/home-overview/home-overview.html + 11 + + + + Ready to take control of your personal finances? + 개인 재정을 관리할 준비가 되셨나요? + + apps/client/src/app/components/home-overview/home-overview.html + 12 + + + + The source code is fully available as open source software (OSS) under the AGPL-3.0 license + 소스 코드는 오픈 소스 소프트웨어로 완전히 공개되어 있으며, AGPL-3.0 라이선스 하에 제공됩니다 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 16 + + + + Setup your accounts + 계정 설정 + + apps/client/src/app/components/home-overview/home-overview.html + 19 + + + + Get a comprehensive financial overview by adding your bank and brokerage accounts. + 은행 및 중개 계좌를 추가하여 포괄적인 재무 개요를 확인하세요. + + apps/client/src/app/components/home-overview/home-overview.html + 21 + + + + Capture your activities + 활동을 캡처하세요 + + apps/client/src/app/components/home-overview/home-overview.html + 28 + + + + Record your investment activities to keep your portfolio up to date. + 투자 활동을 기록하여 포트폴리오를 최신 상태로 유지하세요. + + apps/client/src/app/components/home-overview/home-overview.html + 30 + + + + Monitor and analyze your portfolio + 포트폴리오를 모니터링하고 분석하세요. + + apps/client/src/app/components/home-overview/home-overview.html + 37 + + + + Track your progress in real-time with comprehensive analysis and insights. + 포괄적인 분석과 통찰력을 통해 진행 상황을 실시간으로 추적하세요. + + apps/client/src/app/components/home-overview/home-overview.html + 39 + + + + Setup accounts + 계정 설정 + + apps/client/src/app/components/home-overview/home-overview.html + 52 + + + + Current week + 이번주 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 196 + + + + Add activity + 활동 추가 + + apps/client/src/app/components/home-overview/home-overview.html + 60 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 8 + + + + Total Amount + 총액 + + apps/client/src/app/components/investment-chart/investment-chart.component.ts + 143 + + + + Savings Rate + 저축률 + + apps/client/src/app/components/investment-chart/investment-chart.component.ts + 202 + + + + Security Token + 보안 토큰 + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 8 + + + apps/client/src/app/components/user-account-access/user-account-access.html + 3 + + + apps/client/src/app/components/user-account-access/user-account-access.html + 15 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 279 + + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 64 + + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 72 + + + + or + 또는 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 30 + + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 32 + + + apps/client/src/app/pages/landing/landing-page.html + 47 + + + apps/client/src/app/pages/landing/landing-page.html + 348 + + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 97 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 83 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 161 + + + apps/client/src/app/pages/pricing/pricing-page.html + 329 + + + apps/client/src/app/pages/register/register-page.html + 33 + + + apps/client/src/app/pages/webauthn/webauthn-page.html + 30 + + + + Sign in with Google + 구글로 로그인 + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 44 + + + + Stay signed in + 로그인 상태 유지 + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 66 + + + + Time in Market + 시장 참여 기간 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 3 + + + + Absolute Gross Performance + 절대 총 성과 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 73 + + + + Fees + 수수료 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 208 + + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 88 + + + + Absolute Net Performance + 절대 순 성과 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 107 + + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 193 + + + + Net Performance + 순 성과 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 123 + + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 212 + + + + Total Assets + 총자산 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 149 + + + + Assets + 자산 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 226 + + + + Buying Power + 매수 가능 금액 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 241 + + + + Excluded from Analysis + 분석에서 제외됨 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 267 + + + + Liabilities + 부채 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 295 + + + apps/client/src/app/pages/features/features-page.html + 102 + + + + Net Worth + 순자산 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 317 + + + + Annualized Performance + 연환산 성과 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 331 + + + + Please set the amount of your emergency fund. + 비상금 금액을 설정해 주세요. + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts + 108 + + + + Minimum Price + 최저가 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 130 + + + + Maximum Price + 최고가 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 147 + + + + Quantity + 수량 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 157 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 189 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 193 + + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 74 + + + + Report Data Glitch + 데이터 결함 보고 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 451 + + + + Are you an ambitious investor who needs the full picture? + 당신은 전체 그림이 필요한 야심찬 투자자이신가요? + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 15 + + + + Upgrade to Ghostfolio Premium today and gain access to exclusive features to enhance your investment experience: + 지금 Ghostfolio 프리미엄으로 업그레이드하고, 투자 경험을 향상시키는 독점 기능을 이용하세요: + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 18 + + + + Portfolio Summary + 포트폴리오 요약 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 24 + + + apps/client/src/app/pages/pricing/pricing-page.html + 47 + + + apps/client/src/app/pages/pricing/pricing-page.html + 207 + + + + Portfolio Allocations + 포트폴리오 자산배분 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 28 + + + apps/client/src/app/pages/features/features-page.html + 161 + + + apps/client/src/app/pages/pricing/pricing-page.html + 51 + + + apps/client/src/app/pages/pricing/pricing-page.html + 211 + + + + Performance Benchmarks + 성과 벤치마크 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 32 + + + apps/client/src/app/pages/pricing/pricing-page.html + 55 + + + apps/client/src/app/pages/pricing/pricing-page.html + 215 + + + + FIRE Calculator + 파이어(FIRE) 계산기 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 36 + + + apps/client/src/app/pages/pricing/pricing-page.html + 59 + + + apps/client/src/app/pages/pricing/pricing-page.html + 219 + + + + Professional Data Provider + 전문 데이터 제공자 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 40 + + + apps/client/src/app/pages/pricing/pricing-page.html + 223 + + + + and more Features... + 그리고 더 많은 기능... + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 44 + + + apps/client/src/app/pages/pricing/pricing-page.html + 75 + + + apps/client/src/app/pages/pricing/pricing-page.html + 246 + + + + Get the tools to effectively manage your finances and refine your personal investment strategy. + 재무를 효율적으로 관리하고 개인 투자 전략을 고도화할 수 있는 도구를 제공합니다. + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 48 + + + + Skip + 건너뛰다 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 59 + + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 99 + + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 141 + + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 181 + + + + Upgrade Plan + 업그레이드 계획 + + apps/client/src/app/components/header/header.component.html + 193 + + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 70 + + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 110 + + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 153 + + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 21 + + + apps/client/src/app/pages/pricing/pricing-page.html + 284 + + + + Today + 오늘 + + apps/client/src/app/pages/public/public-page.html + 24 + + + libs/ui/src/lib/assistant/assistant.component.ts + 365 + + + + YTD + 연초 대비 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 204 + + + libs/ui/src/lib/assistant/assistant.component.ts + 377 + + + + 1Y + 1년 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 208 + + + libs/ui/src/lib/assistant/assistant.component.ts + 387 + + + + 5Y + 5년 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 212 + + + libs/ui/src/lib/assistant/assistant.component.ts + 411 + + + + Max + 맥스 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 216 + + + libs/ui/src/lib/assistant/assistant.component.ts + 417 + + + + Grant access + 액세스 권한 부여 + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 9 + + + + Public + 공공의 + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 31 + + + + Granted Access + 액세스 권한 부여 + + apps/client/src/app/components/user-account-access/user-account-access.html + 57 + + + + Please enter your coupon code. + 쿠폰 코드를 입력해주세요. + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 218 + + + + Could not redeem coupon code + 쿠폰 코드를 사용할 수 없습니다. + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 182 + + + + Coupon code has been redeemed + 쿠폰 코드가 사용되었습니다. + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 195 + + + + Reload + 새로고침 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 196 + + + + per year + 연간 + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 33 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 80 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 158 + + + apps/client/src/app/pages/pricing/pricing-page.html + 268 + + + + Try Premium + 프리미엄을 사용해 보세요 + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 53 + + + + Redeem Coupon + 쿠폰 사용 + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 67 + + + + Auto + 자동 + + apps/client/src/app/components/user-account-settings/user-account-settings.component.ts + 69 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 172 + + + + Do you really want to remove this sign in method? + 이 로그인 방법을 정말로 제거하시겠습니까? + + apps/client/src/app/components/user-account-settings/user-account-settings.component.ts + 281 + + + + Presenter View + 발표자 보기 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 183 + + + + Protection for sensitive information like absolute performances and quantity values + 절대 성과 및 수량 값과 같은 민감한 정보를 보호합니다. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 185 + + + + Base Currency + 기본 통화 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 9 + + + + Language + 언어 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 56 + + + + Locale + 장소 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 448 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 133 + + + + Date and number format + 날짜 및 숫자 형식 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 135 + + + + Appearance + 테마 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 158 + + + + Light + 라이트 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 173 + + + + Dark + 다크 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 174 + + + + Zen Mode + 프라이버시 모드 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 201 + + + apps/client/src/app/pages/features/features-page.html + 246 + + + + Distraction-free experience for turbulent times + 격동의 시대에 방해받지 않는 경험 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 203 + + + + this is projected to increase to + 이는 다음과 같이 증가할 것으로 예상된다. + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 147 + + + + Biometric Authentication + 생체인증 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 218 + + + + Sign in with fingerprint + 지문으로 로그인 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 219 + + + + Experimental Features + 실험적 기능 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 235 + + + + Sneak peek at upcoming functionality + 곧 출시될 기능 미리보기 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 237 + + + + User ID + 사용자 ID + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 51 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 252 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 11 + + + + Export Data + 데이터 내보내기 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 260 + + + + This feature is currently unavailable. + 이 기능은 현재 사용할 수 없습니다. + + apps/client/src/app/core/http-response.interceptor.ts + 55 + + + + Please try again later. + 나중에 다시 시도해 주세요. + + apps/client/src/app/core/http-response.interceptor.ts + 57 + + + apps/client/src/app/core/http-response.interceptor.ts + 88 + + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 195 + + + + Oops! Something went wrong. + 이런! 문제가 발생했습니다. + + apps/client/src/app/core/http-response.interceptor.ts + 86 + + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 193 + + + + Okay + 좋아요 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 157 + + + apps/client/src/app/core/http-response.interceptor.ts + 89 + + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 196 + + + + About + 소개 + + apps/client/src/app/components/footer/footer.component.html + 20 + + + apps/client/src/app/components/header/header.component.html + 124 + + + apps/client/src/app/components/header/header.component.html + 375 + + + apps/client/src/app/pages/about/overview/about-overview-page.routes.ts + 12 + + + libs/common/src/lib/routes/routes.ts + 220 + + + + Changelog + 변경 내역 + + apps/client/src/app/components/footer/footer.component.html + 27 + + + apps/client/src/app/pages/about/changelog/changelog-page.html + 4 + + + libs/common/src/lib/routes/routes.ts + 185 + + + + License + 특허 + + apps/client/src/app/components/footer/footer.component.html + 39 + + + apps/client/src/app/pages/about/license/license-page.html + 4 + + + libs/common/src/lib/routes/routes.ts + 193 + + + + Privacy Policy + 개인 정보 보호 정책 + + apps/client/src/app/components/footer/footer.component.html + 55 + + + apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html + 4 + + + libs/common/src/lib/routes/routes.ts + 209 + + + + Our + 우리의 + + apps/client/src/app/pages/about/oss-friends/oss-friends-page.html + 6 + + + + Discover other exciting Open Source Software projects + 다른 흥미로운 오픈 소스 소프트웨어 프로젝트를 찾아보세요 + + apps/client/src/app/pages/about/oss-friends/oss-friends-page.html + 9 + + + + Visit + 방문하다 + + apps/client/src/app/pages/about/oss-friends/oss-friends-page.html + 28 + + + + for + ~을 위한 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 128 + + + + Accounts + 계정 + + apps/client/src/app/components/admin-platform/admin-platform.component.html + 52 + + + apps/client/src/app/components/admin-users/admin-users.html + 97 + + + apps/client/src/app/components/header/header.component.html + 58 + + + apps/client/src/app/components/header/header.component.html + 268 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 375 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 66 + + + apps/client/src/app/pages/accounts/accounts-page.html + 4 + + + libs/common/src/lib/routes/routes.ts + 69 + + + libs/ui/src/lib/assistant/assistant.html + 84 + + + + Oops, cash balance transfer has failed. + 죄송합니다. 현금 잔액 이체가 실패했습니다. + + apps/client/src/app/pages/accounts/accounts-page.component.ts + 341 + + + + Update account + 계정 업데이트 + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 8 + + + + Add account + 계정 추가 + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 10 + + + + Account ID + 계정 ID + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 96 + + + + From + 에서 + + apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.html + 11 + + + + To + 에게 + + apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.html + 32 + + + + Transfer + 옮기다 + + apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.html + 72 + + + + Admin Control + 관리자 제어 + + apps/client/src/app/components/header/header.component.html + 74 + + + apps/client/src/app/components/header/header.component.html + 289 + + + libs/common/src/lib/routes/routes.ts + 64 + + + + Market Data + 시장 데이터 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 398 + + + libs/common/src/lib/routes/routes.ts + 51 + + + + Settings + 설정 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 2 + + + libs/common/src/lib/routes/routes.ts + 34 + + + libs/common/src/lib/routes/routes.ts + 56 + + + + Users + 사용자 + + libs/common/src/lib/routes/routes.ts + 61 + + + + Overview + 개요 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 113 + + + apps/client/src/app/components/header/header.component.html + 30 + + + apps/client/src/app/components/header/header.component.html + 248 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 47 + + + apps/client/src/app/pages/admin/admin-page.component.ts + 48 + + + apps/client/src/app/pages/resources/resources-page.component.ts + 30 + + + libs/common/src/lib/routes/routes.ts + 113 + + + libs/common/src/lib/routes/routes.ts + 170 + + + + Blog + 블로그 + + apps/client/src/app/components/footer/footer.component.html + 24 + + + apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html + 205 + + + apps/client/src/app/pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.html + 185 + + + apps/client/src/app/pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.html + 185 + + + apps/client/src/app/pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.html + 185 + + + 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.html + 210 + + + apps/client/src/app/pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.html + 197 + + + apps/client/src/app/pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.html + 182 + + + apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.html + 142 + + + apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.html + 169 + + + apps/client/src/app/pages/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt/ghostfolio-auf-sackgeld-vorgestellt-page.html + 179 + + + apps/client/src/app/pages/blog/2023/02/ghostfolio-meets-umbrel/ghostfolio-meets-umbrel-page.html + 203 + + + apps/client/src/app/pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.html + 254 + + + apps/client/src/app/pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.html + 234 + + + apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.html + 244 + + + apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.html + 155 + + + apps/client/src/app/pages/blog/2023/09/ghostfolio-2/ghostfolio-2-page.html + 274 + + + apps/client/src/app/pages/blog/2023/09/hacktoberfest-2023/hacktoberfest-2023-page.html + 184 + + + apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.html + 149 + + + apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.html + 271 + + + apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html + 190 + + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 168 + + + apps/client/src/app/pages/blog/2025/09/hacktoberfest-2025/hacktoberfest-2025-page.html + 189 + + + apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html + 147 + + + apps/client/src/app/pages/blog/blog-page.html + 5 + + + libs/common/src/lib/routes/routes.ts + 225 + + + + Discover the latest Ghostfolio updates and insights on personal finance + 개인 금융에 대한 최신 Ghostfolio 업데이트와 통찰력을 알아보세요 + + apps/client/src/app/pages/blog/blog-page.html + 7 + + + + As you are already logged in, you cannot access the demo account. + 이미 로그인되어 있으므로 데모 계정에 접근할 수 없습니다. + + apps/client/src/app/pages/demo/demo-page.component.ts + 35 + + + + Frequently Asked Questions (FAQ) + 자주 묻는 질문 + + apps/client/src/app/components/footer/footer.component.html + 33 + + + apps/client/src/app/pages/about/overview/about-overview-page.html + 189 + + + apps/client/src/app/pages/faq/overview/faq-overview-page.routes.ts + 12 + + + libs/common/src/lib/routes/routes.ts + 251 + + + + Features + 특징 + + apps/client/src/app/components/footer/footer.component.html + 29 + + + apps/client/src/app/components/header/header.component.html + 361 + + + apps/client/src/app/pages/features/features-page.html + 5 + + + libs/common/src/lib/routes/routes.ts + 256 + + + + Check out the numerous features of Ghostfolio to manage your wealth + 자산 관리를 위한 Ghostfolio의 다양한 기능을 확인해보세요 + + apps/client/src/app/pages/features/features-page.html + 7 + + + + ETFs + ETF + + apps/client/src/app/pages/features/features-page.html + 25 + + + + Bonds + 채권 + + apps/client/src/app/pages/features/features-page.html + 38 + + + + Wealth Items + 자산 항목 + + apps/client/src/app/pages/features/features-page.html + 76 + + + + Import and Export + 가져오기 및 내보내기 + + apps/client/src/app/pages/features/features-page.html + 116 + + + + Multi-Accounts + 다중 계정 + + apps/client/src/app/pages/features/features-page.html + 127 + + + + Portfolio Calculations + 포트폴리오 계산 + + apps/client/src/app/pages/features/features-page.html + 141 + + + + Dark Mode + 다크 모드 + + apps/client/src/app/pages/features/features-page.html + 233 + + + + Market Mood + 시장 분위기 + + apps/client/src/app/pages/features/features-page.html + 215 + + + + Static Analysis + 정적 분석 + + apps/client/src/app/pages/features/features-page.html + 179 + + + + Multi-Language + 다국어 + + apps/client/src/app/pages/features/features-page.html + 259 + + + + per week + 주당 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 130 + + + + Open Source Software + 오픈 소스 소프트웨어 + + apps/client/src/app/pages/features/features-page.html + 295 + + + + Get Started + 시작하기 + + apps/client/src/app/components/header/header.component.html + 433 + + + apps/client/src/app/pages/features/features-page.html + 320 + + + apps/client/src/app/pages/landing/landing-page.html + 41 + + + apps/client/src/app/pages/landing/landing-page.html + 344 + + + apps/client/src/app/pages/pricing/pricing-page.html + 363 + + + apps/client/src/app/pages/public/public-page.html + 242 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + + + + Holdings + 보유 종목 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 102 + + + apps/client/src/app/components/home-holdings/home-holdings.html + 4 + + + apps/client/src/app/pages/public/public-page.html + 70 + + + libs/common/src/lib/routes/routes.ts + 90 + + + libs/common/src/lib/routes/routes.ts + 167 + + + libs/ui/src/lib/assistant/assistant.html + 110 + + + + Summary + 요약 + + apps/client/src/app/components/home-summary/home-summary.html + 2 + + + libs/common/src/lib/routes/routes.ts + 105 + + + + Markets + 시장 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 380 + + + apps/client/src/app/components/footer/footer.component.html + 11 + + + apps/client/src/app/components/header/header.component.html + 408 + + + apps/client/src/app/components/home-market/home-market.html + 2 + + + apps/client/src/app/components/markets/markets.html + 2 + + + apps/client/src/app/pages/resources/markets/resources-markets.component.html + 2 + + + apps/client/src/app/pages/resources/resources-page.component.ts + 40 + + + libs/common/src/lib/routes/routes.ts + 95 + + + libs/common/src/lib/routes/routes.ts + 100 + + + libs/common/src/lib/routes/routes.ts + 261 + + + libs/common/src/lib/routes/routes.ts + 309 + + + + Ghostfolio is a personal finance dashboard to keep track of your net worth including cash, stocks, ETFs and cryptocurrencies across multiple platforms. + Ghostfolio는 여러 플랫폼에 분산된 현금, 주식, ETF, 암호화폐를 포함한 순자산을 추적할 수 있는 개인 재무 대시보드입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 5 + + + + app, asset, cryptocurrency, dashboard, etf, finance, management, performance, portfolio, software, stock, trading, wealth, web3 + 앱, 자산, 암호화폐, 대시보드, ETF, 금융, 관리, 성과, 포트폴리오, 소프트웨어, 주식, 트레이딩, 자산관리, 웹3 + + apps/client/src/app/pages/i18n/i18n-page.html + 10 + + + + Open Source Wealth Management Software + 오픈 소스 자산관리 소프트웨어 + + apps/client/src/app/pages/i18n/i18n-page.html + 237 + + + + Manage your wealth like a boss + 전문가처럼 자산을 관리하세요 + + apps/client/src/app/pages/landing/landing-page.html + 6 + + + + Ghostfolio is a privacy-first, open source dashboard for your personal finances. Break down your asset allocation, know your net worth and make solid, data-driven investment decisions. + Ghostfolio는 프라이버시를 최우선으로 하는 오픈 소스 개인 재무 대시보드입니다. 자산배분을 분석하고 순자산을 파악하여, 데이터 기반의 합리적인 투자 의사결정을 내리세요. + + apps/client/src/app/pages/landing/landing-page.html + 10 + + + + Edit access + 접근 권한 편집 + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 11 + + + + Monthly Active Users + 월간 활성 사용자 + + apps/client/src/app/pages/landing/landing-page.html + 69 + + + + Stars on GitHub + 깃허브 스타 + + apps/client/src/app/pages/landing/landing-page.html + 87 + + + apps/client/src/app/pages/open/open-page.html + 103 + + + + Pulls on Docker Hub + 도커 허브에서 가져오기 + + apps/client/src/app/pages/landing/landing-page.html + 105 + + + apps/client/src/app/pages/open/open-page.html + 117 + + + + As seen in + 에서 볼 수 있듯이 + + apps/client/src/app/pages/landing/landing-page.html + 114 + + + + Protect your assets. Refine your personal investment strategy. + 자산을 보호하세요. 개인 투자 전략을 정교화하세요. + + apps/client/src/app/pages/landing/landing-page.html + 124 + + + + Ghostfolio empowers busy people to keep track of stocks, ETFs or cryptocurrencies without being tracked. + Ghostfolio는 바쁜 사람들이 추적당하지 않으면서도 주식, ETF, 암호화폐를 손쉽게 추적할 수 있도록 돕습니다. + + apps/client/src/app/pages/landing/landing-page.html + 128 + + + + 360° View + 360° 보기 + + apps/client/src/app/pages/landing/landing-page.html + 138 + + + + Get the full picture of your personal finances across multiple platforms. + 여러 플랫폼에 걸쳐 개인 재정에 대한 전체 그림을 얻으세요. + + apps/client/src/app/pages/landing/landing-page.html + 141 + + + + Web3 Ready + 웹3 준비 + + apps/client/src/app/pages/landing/landing-page.html + 149 + + + + Use Ghostfolio anonymously and own your financial data. + 익명으로 Ghostfolio를 사용하고 금융 데이터를 소유하세요. + + apps/client/src/app/pages/landing/landing-page.html + 152 + + + + Benefit from continuous improvements through a strong community. + 강력한 커뮤니티를 통해 지속적인 개선의 혜택을 누리세요. + + apps/client/src/app/pages/landing/landing-page.html + 162 + + + + Get access to 80’000+ tickers from over 50 exchanges + 50개 이상의 거래소에서 80,000개 이상의 티커에 접근하세요 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 84 + + + + Why Ghostfolio? + Ghostfolio인가요? + + apps/client/src/app/pages/landing/landing-page.html + 170 + + + + Ghostfolio is for you if you are... + Ghostfolio는 당신을 위한 것입니다... + + apps/client/src/app/pages/landing/landing-page.html + 172 + + + + trading stocks, ETFs or cryptocurrencies on multiple platforms + 여러 플랫폼에서 주식, ETF 또는 암호화폐 거래 + + apps/client/src/app/pages/landing/landing-page.html + 178 + + + + pursuing a buy & hold strategy + 매수 후 보유 전략을 추구 + + apps/client/src/app/pages/landing/landing-page.html + 184 + + + + interested in getting insights of your portfolio composition + 포트폴리오 구성에 대한 인사이트가 필요하신가요 + + apps/client/src/app/pages/landing/landing-page.html + 189 + + + + valuing privacy and data ownership + 개인 정보 보호 및 데이터 소유권을 소중히 여깁니다. + + apps/client/src/app/pages/landing/landing-page.html + 194 + + + + into minimalism + 미니멀리즘으로 + + apps/client/src/app/pages/landing/landing-page.html + 197 + + + + caring about diversifying your financial resources + 재정 자원을 다양화하는 데 관심을 가짐 + + apps/client/src/app/pages/landing/landing-page.html + 201 + + + + interested in financial independence + 재정적 독립에 관심 + + apps/client/src/app/pages/landing/landing-page.html + 205 + + + + saying no to spreadsheets in + 의 스프레드시트에 거부 의사 표시 + + apps/client/src/app/pages/landing/landing-page.html + 209 + + + + still reading this list + 아직도 이 목록을 읽고 있어요 + + apps/client/src/app/pages/landing/landing-page.html + 212 + + + + Learn more about Ghostfolio + Ghostfolio에 대해 자세히 알아보기 + + apps/client/src/app/pages/landing/landing-page.html + 217 + + + + What our users are saying + 사용자의 이야기 + + apps/client/src/app/pages/landing/landing-page.html + 226 + + + + Members from around the globe are using Ghostfolio Premium + 전 세계의 사용자들이 Ghostfolio 프리미엄을 사용하고 있습니다 + + apps/client/src/app/pages/landing/landing-page.html + 265 + + + + How does Ghostfolio work? + Ghostfolio는 어떻게 동작하나요? + + apps/client/src/app/pages/landing/landing-page.html + 282 + + + + Get started in only 3 steps + 단 3단계만으로 시작하세요 + + apps/client/src/app/pages/landing/landing-page.html + 284 + + + + less than + 미만 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 129 + + + + Sign up anonymously* + 익명으로 가입* + + apps/client/src/app/pages/landing/landing-page.html + 290 + + + + * no e-mail address nor credit card required + * 이메일 주소 및 신용카드 정보가 필요하지 않습니다 + + apps/client/src/app/pages/landing/landing-page.html + 292 + + + + Add any of your historical transactions + 과거 거래를 추가하세요. + + apps/client/src/app/pages/landing/landing-page.html + 304 + + + + Get valuable insights of your portfolio composition + 포트폴리오 구성에 대한 유의미한 인사이트를 확인하세요 + + apps/client/src/app/pages/landing/landing-page.html + 316 + + + + Are you ready? + 준비되셨나요? + + apps/client/src/app/pages/landing/landing-page.html + 330 + + + + 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 라이선스 하에 배포합니다. 또한 플랫폼 운영 현황에 대한 집계된 핵심 지표를 공개적으로 공유합니다. + + apps/client/src/app/pages/open/open-page.html + 7 + + + + (Last 24 hours) + (지난 24시간) + + apps/client/src/app/pages/open/open-page.html + 37 + + + + Ghostfolio Status + 고스트폴리오 상태 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 62 + + + + with your university e-mail address + 대학 이메일 주소로 + + apps/client/src/app/pages/pricing/pricing-page.html + 351 + + + + Active Users + 활성 사용자 + + apps/client/src/app/pages/open/open-page.html + 40 + + + apps/client/src/app/pages/open/open-page.html + 62 + + + + (Last 30 days) + (지난 30일) + + apps/client/src/app/pages/open/open-page.html + 48 + + + apps/client/src/app/pages/open/open-page.html + 59 + + + + and a safe withdrawal rate (SWR) of + 안전 인출률(SWR)은 다음과 같습니다. + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 108 + + + + New Users + 신규 사용자 + + apps/client/src/app/pages/open/open-page.html + 51 + + + + Users in Slack community + 슬랙 커뮤니티의 사용자 + + apps/client/src/app/pages/open/open-page.html + 75 + + + + Job ID + 작업 ID + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 34 + + + + Contributors on GitHub + 깃허브의 기여자 + + apps/client/src/app/pages/open/open-page.html + 89 + + + + (Last 90 days) + (지난 90일) + + apps/client/src/app/pages/open/open-page.html + 127 + + + + Uptime + 가동 시간 + + apps/client/src/app/pages/open/open-page.html + 132 + + + + Activities + 활동 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 86 + + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 115 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 228 + + + apps/client/src/app/components/admin-tag/admin-tag.component.html + 45 + + + apps/client/src/app/components/admin-users/admin-users.html + 118 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 231 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 342 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 75 + + + apps/client/src/app/pages/portfolio/activities/activities-page.html + 4 + + + libs/common/src/lib/routes/routes.ts + 128 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 119 + + + + Do you really want to delete these activities? + 정말로 이 활동을 삭제하시겠습니까? + + libs/ui/src/lib/activities-table/activities-table.component.ts + 278 + + + + Update activity + 활동 업데이트 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 10 + + + + Stocks, ETFs, bonds, cryptocurrencies, commodities + 주식, ETF, 채권, 암호화폐, 원자재 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 25 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 65 + + + + One-time fee, annual account fees + 일회성 수수료, 연간 계정 수수료 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 33 + + + + Distribution of corporate earnings + 기업 수익 분배 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 41 + + + + Revenue for lending out money + 이자 수익 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 49 + + + + Mortgages, personal loans, credit cards + 모기지, 개인 대출, 신용카드 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 57 + + + + Luxury items, real estate, private companies + 명품, 부동산, 민간 기업 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 73 + + + + Update Cash Balance + 현금 잔액 업데이트 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 112 + + + + Unit Price + 단가 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 214 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 217 + + + + Import Activities + 활동 가져오기 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 92 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 9 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 383 + + + + Import Dividends + 배당금 가져오기 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 137 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 29 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 397 + + + + Importing data... + 데이터 가져오는 중... + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 175 + + + + Import has been completed + 가져오기가 완료되었습니다. + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 185 + + + + or start a discussion at + 또는 다음에서 토론을 시작하세요. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 94 + + + + Validating data... + 데이터 유효성을 검사하는 중... + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts + 299 + + + + Select Holding + 보유 종목 선택 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 19 + + + + Select File + 파일 선택 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 21 + + + + Holding + 보유 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 32 + + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 + + + + Load Dividends + 배당금 불러오기 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 68 + + + + Choose or drop a file here + 여기에 파일을 선택하거나 드롭하세요. + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 84 + + + + The following file formats are supported: + 다음 파일 형식이 지원됩니다. + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 90 + + + + Select Dividends + 배당금 선택 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 113 + + + + Select Activities + 활동 선택 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 115 + + + + Back + 뒤쪽에 + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 146 + + + apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html + 182 + + + + Allocations + 할당 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 4 + + + apps/client/src/app/pages/portfolio/allocations/allocations-page.routes.ts + 12 + + + libs/common/src/lib/routes/routes.ts + 133 + + + + Proportion of Net Worth + 순자산 비율 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 12 + + + + By Platform + 플랫폼별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 44 + + + + By Currency + 통화별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 63 + + + + By Asset Class + 자산 클래스별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 85 + + + + By Holding + 보유 종목별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 107 + + + + By Sector + 부문별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 130 + + + + By Continent + 대륙별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 153 + + + + By Market + 시장별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 175 + + + + Regions + 지역 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 198 + + + apps/client/src/app/pages/public/public-page.html + 151 + + + + Exclude from Analysis + 분석에서 제외 + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 90 + + + libs/ui/src/lib/i18n.ts + 17 + + + + Developed Markets + 선진시장 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 222 + + + apps/client/src/app/pages/public/public-page.html + 168 + + + + Latest activities + 최신 활동 + + apps/client/src/app/pages/public/public-page.html + 211 + + + + Emerging Markets + 신흥시장 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 231 + + + apps/client/src/app/pages/public/public-page.html + 177 + + + + Other Markets + 기타 시장 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 240 + + + apps/client/src/app/pages/public/public-page.html + 186 + + + + By Account + 계정별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 286 + + + + By ETF Provider + ETF 제공자별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 306 + + + + By Country + 국가별 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 264 + + + + Analysis + 분석 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 2 + + + libs/common/src/lib/routes/routes.ts + 138 + + + + Looking for a student discount? + 학생 할인을 찾고 계십니까? + + apps/client/src/app/pages/pricing/pricing-page.html + 345 + + + + Dividend + 배당금 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 81 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 186 + + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 365 + + + apps/client/src/app/pages/features/features-page.html + 63 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 202 + + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 75 + + + libs/ui/src/lib/i18n.ts + 38 + + + + annual interest rate + 연간 이자율 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 185 + + + + Deposit + 보증금 + + libs/ui/src/lib/fire-calculator/fire-calculator.component.ts + 377 + + + + Monthly + 월간 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 90 + + + + Yearly + 매년 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 91 + + + + Top + 상위 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 239 + + + + Bottom + 하위 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 288 + + + + Portfolio Evolution + 포트폴리오 진화 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 341 + + + + Investment Timeline + 투자 일정 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 368 + + + + Current Streak + 현재 연속 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 389 + + + + Longest Streak + 최장 연속 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 398 + + + + Dividend Timeline + 배당 일정 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 425 + + + + FIRE + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 4 + + + + Calculator + 계산자 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 7 + + + + Pricing + 가격 + + apps/client/src/app/components/footer/footer.component.html + 49 + + + apps/client/src/app/components/header/header.component.html + 105 + + + apps/client/src/app/components/header/header.component.html + 313 + + + apps/client/src/app/components/header/header.component.html + 389 + + + apps/client/src/app/pages/pricing/pricing-page.routes.ts + 12 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 287 + + + libs/common/src/lib/routes/routes.ts + 271 + + + + Pricing Plans + 가격 계획 + + apps/client/src/app/pages/pricing/pricing-page.html + 4 + + + + Our official Ghostfolio Premium cloud offering is the easiest way to get started. Due to the time it saves, this will be the best option for most people. Revenue is used to cover operational costs for the hosting infrastructure and professional data providers, and to fund ongoing development. + 공식 Ghostfolio 프리미엄 클라우드 서비스는 시작하는 가장 쉬운 방법입니다. 시간이 절약되므로 이는 대부분의 사람들에게 최선의 선택이 될 것입니다. 수익은 호스팅 인프라 및 전문 데이터 제공업체의 운영 비용을 충당하고 지속적인 개발 자금을 조달하는 데 사용됩니다. + + apps/client/src/app/pages/pricing/pricing-page.html + 7 + + + + If you prefer to run Ghostfolio on your own infrastructure, please find the source code and further instructions on GitHub. + 자체 인프라에서 Ghostfolio를 운영하고 싶다면, 깃허브에서 소스 코드와 추가 안내를 확인하세요. + + apps/client/src/app/pages/pricing/pricing-page.html + 14 + + + + For tech-savvy investors who prefer to run Ghostfolio on their own infrastructure. + 자체 인프라에서 Ghostfolio를 실행하기를 선호하는 기술에 정통한 투자자를 위한 것입니다. + + apps/client/src/app/pages/pricing/pricing-page.html + 26 + + + + Unlimited Transactions + 무제한 거래 + + apps/client/src/app/pages/pricing/pricing-page.html + 35 + + + apps/client/src/app/pages/pricing/pricing-page.html + 127 + + + + Unlimited Accounts + 무제한 계정 + + apps/client/src/app/pages/pricing/pricing-page.html + 39 + + + apps/client/src/app/pages/pricing/pricing-page.html + 131 + + + + Portfolio Performance + 포트폴리오 성과 + + apps/client/src/app/pages/pricing/pricing-page.html + 43 + + + apps/client/src/app/pages/pricing/pricing-page.html + 135 + + + + Data Import and Export + 데이터 가져오기 및 내보내기 + + apps/client/src/app/pages/pricing/pricing-page.html + 63 + + + apps/client/src/app/pages/pricing/pricing-page.html + 139 + + + + Community Support + 커뮤니티 지원 + + apps/client/src/app/pages/pricing/pricing-page.html + 80 + + + + Self-hosted, update manually. + 자체 호스팅, 수동으로 업데이트합니다. + + apps/client/src/app/pages/pricing/pricing-page.html + 84 + + + + Free + 무료 + + apps/client/src/app/pages/pricing/pricing-page.html + 86 + + + apps/client/src/app/pages/pricing/pricing-page.html + 152 + + + + For new investors who are just getting started with trading. + 이제 막 거래를 시작한 신규 투자자를 위한 제품입니다. + + apps/client/src/app/pages/pricing/pricing-page.html + 119 + + + + Fully managed Ghostfolio cloud offering. + 완전 관리형 Ghostfolio 클라우드 제품. + + apps/client/src/app/pages/pricing/pricing-page.html + 150 + + + apps/client/src/app/pages/pricing/pricing-page.html + 255 + + + + For ambitious investors who need the full picture of their financial assets. + 자신의 금융 자산에 대한 전체 그림이 필요한 야심찬 투자자를 위한 제품입니다. + + apps/client/src/app/pages/pricing/pricing-page.html + 193 + + + + Email and Chat Support + 이메일 및 채팅 지원 + + apps/client/src/app/pages/pricing/pricing-page.html + 251 + + + + Renew Plan + 플랜 갱신 + + apps/client/src/app/components/header/header.component.html + 191 + + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 19 + + + apps/client/src/app/pages/pricing/pricing-page.html + 282 + + + + One-time payment, no auto-renewal. + 일회성 결제, 자동 갱신 없음. + + apps/client/src/app/pages/pricing/pricing-page.html + 288 + + + + It’s free. + 무료입니다. + + apps/client/src/app/pages/pricing/pricing-page.html + 365 + + + + Hello, has shared a Portfolio with you! + 안녕하세요. 님이 포트폴리오를 공유했습니다! + + apps/client/src/app/pages/public/public-page.html + 5 + + + + Continents + 대륙 + + apps/client/src/app/pages/public/public-page.html + 132 + + + + Sustainable retirement income + 지속 가능한 퇴직 소득 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 41 + + + + Ghostfolio empowers you to keep track of your wealth. + Ghostfolio는 귀하의 재산을 추적할 수 있도록 해줍니다. + + apps/client/src/app/pages/public/public-page.html + 238 + + + + Registration + 등록 + + apps/client/src/app/components/admin-users/admin-users.html + 80 + + + libs/common/src/lib/routes/routes.ts + 281 + + + + Continue with Google + 구글로 계속하기 + + apps/client/src/app/pages/register/register-page.html + 43 + + + + Copy to clipboard + 클립보드에 복사 + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 88 + + + + Personal Finance Tools + 개인 금융 도구 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 351 + + + libs/common/src/lib/routes/routes.ts + 329 + + + + open-source-alternative-to + open-source-alternative-to + kebab-case + + libs/common/src/lib/routes/routes.ts + 320 + + + libs/common/src/lib/routes/routes.ts + 324 + + + + Discover Open Source Alternatives for Personal Finance Tools + 개인 금융 도구를 위한 오픈 소스 대안을 찾아보세요 + + apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html + 5 + + + + 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. + 이 개요 페이지에는 오픈 소스 대안인 Ghostfolio와 비교하여 엄선된 개인 금융 도구 컬렉션이 포함되어 있습니다. 투명성, 데이터 개인 정보 보호 및 커뮤니티 협업을 중요하게 생각한다면 Ghostfolio는 재무 관리를 제어할 수 있는 훌륭한 기회를 제공합니다. + + apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html + 9 + + + + Explore the links below to compare a variety of personal finance tools with Ghostfolio. + Ghostfolio와 다양한 개인 금융 도구를 비교하려면 아래 링크를 탐색하십시오. + + apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html + 17 + + + + Open Source Alternative to + 의 오픈 소스 대안 + + apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html + 42 + + + + The Open Source Alternative to + 오픈 소스 대안 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 8 + + + + Are you looking for an open source alternative to ? Ghostfolio is a powerful portfolio management tool that provides individuals with a comprehensive platform to track, analyze, and optimize their investments. Whether you are an experienced investor or just starting out, Ghostfolio offers an intuitive user interface and a wide range of functionalities to help you make informed decisions and take control of your financial future. + 에 대한 오픈소스 대안을 찾고 계십니까? Ghostfolio는 개인에게 투자를 추적, 분석, 최적화할 수 있는 포괄적인 플랫폼을 제공하는 강력한 포트폴리오 관리 도구입니다. 숙련된 투자자이든 이제 막 시작한 투자자이든 Ghostfolio는 직관적인 사용자 인터페이스와 다양한 기능을 제공하여 정보에 입각한 결정을 내리고 재정적 미래를 관리하는 데 도움을 줍니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 19 + + + + Ghostfolio is an open source software (OSS), providing a cost-effective alternative to making it particularly suitable for individuals on a tight budget, such as those pursuing Financial Independence, Retire Early (FIRE). By leveraging the collective efforts of a community of developers and personal finance enthusiasts, Ghostfolio continuously enhances its capabilities, security, and user experience. + Ghostfolio는 오픈 소스 소프트웨어로, 에 대한 비용 효율적인 대안을 제공하므로 재정적 독립, 조기 퇴직(FIRE)을 추구하는 사람과 같이 예산이 부족한 개인에게 특히 적합합니다. Ghostfolio는 개발자 커뮤니티와 개인 금융 애호가들의 공동 노력을 활용하여 기능, 보안 및 사용자 경험을 지속적으로 향상시킵니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 33 + + + + Let’s dive deeper into the detailed Ghostfolio vs comparison table below to gain a thorough understanding of how Ghostfolio positions itself relative to . We will explore various aspects such as features, data privacy, pricing, and more, allowing you to make a well-informed choice for your personal requirements. + Ghostfolio가 과 관련하여 어떻게 위치하는지 철저하게 이해하기 위해 아래의 자세한 Ghostfolio 대 비교표를 자세히 살펴보겠습니다. 우리는 기능, 데이터 개인 정보 보호, 가격 등과 같은 다양한 측면을 탐색하여 귀하의 개인 요구 사항에 맞는 정보를 바탕으로 선택할 수 있도록 할 것입니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 44 + + + + per month + 매월 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 94 + + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 172 + + + + Ghostfolio vs comparison table + Ghostfolio와 비교표 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 55 + + + + Website of Thomas Kaul + 토마스 카울의 웹사이트 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 44 + + + + Founded + 설립 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 77 + + + + Origin + 기원 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 82 + + + + Region + 지역 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 87 + + + + Available in + 사용 가능 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 109 + + + + ✅ Yes + ✅ 예 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 140 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 157 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 179 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 196 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 218 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 235 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 257 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 274 + + + + ❌ No + ❌ 아니요 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 147 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 164 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 186 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 203 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 225 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 242 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 264 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 281 + + + + Self-Hosting + 셀프 호스팅 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 171 + + + + Use anonymously + 익명으로 사용 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 210 + + + + Free Plan + 무료 플랜 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 249 + + + + Starting from + 에서 시작 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 289 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 294 + + + + Notes + 메모 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 302 + + + + Please note that the information provided in the Ghostfolio vs comparison table is based on our independent research and analysis. This website is not affiliated with or any other product mentioned in the comparison. As the landscape of personal finance tools evolves, it is essential to verify any specific details or changes directly from the respective product page. Data needs a refresh? Help us maintain accurate data on GitHub. + Ghostfolio와 비교표에 제공된 정보는 당사의 독립적인 연구 및 분석을 기반으로 한 것입니다. 이 웹사이트는 또는 비교에 언급된 다른 제품과 관련이 없습니다. 개인 금융 도구의 환경이 발전함에 따라 각 제품 페이지에서 직접 특정 세부 정보나 변경 사항을 확인하는 것이 중요합니다. 데이터를 새로 고쳐야 합니까? 깃허브에서 정확한 데이터를 유지할 수 있도록 도와주세요. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 312 + + + + Ready to take your investments to the next level? + 투자다음 단계로 발전시킬 준비가 되셨나요? + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 325 + + + + Effortlessly track, analyze, and visualize your wealth with Ghostfolio. + Ghostfolio를 사용하여 귀하의 재산을 쉽게 추적, 분석 및 시각화하십시오. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 329 + + + + Switzerland + 스위스 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 57 + + + libs/ui/src/lib/i18n.ts + 99 + + + + Global + 글로벌 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 58 + + + libs/ui/src/lib/i18n.ts + 18 + + + + Resources + 리소스 + + apps/client/src/app/components/footer/footer.component.html + 14 + + + apps/client/src/app/components/header/header.component.html + 88 + + + apps/client/src/app/components/header/header.component.html + 301 + + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 4 + + + libs/common/src/lib/routes/routes.ts + 332 + + + + Membership + 멤버십 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 48 + + + libs/common/src/lib/routes/routes.ts + 31 + + + libs/ui/src/lib/membership-card/membership-card.component.html + 40 + + + + Request it + 요청하세요 + + apps/client/src/app/pages/pricing/pricing-page.html + 347 + + + + Access + 입장 + + libs/common/src/lib/routes/routes.ts + 26 + + + + My Ghostfolio + 나의 고스트폴리오 + + apps/client/src/app/components/header/header.component.html + 277 + + + apps/client/src/app/pages/user-account/user-account-page.routes.ts + 33 + + + + Oops, authentication has failed. + 앗, 인증에 실패했습니다. + + apps/client/src/app/pages/webauthn/webauthn-page.html + 19 + + + + Try again + 다시 시도하세요 + + apps/client/src/app/pages/webauthn/webauthn-page.html + 27 + + + + Go back to Home Page + 홈 페이지로 돌아가기 + + apps/client/src/app/pages/webauthn/webauthn-page.html + 33 + + + + Do you really want to delete this account balance? + 정말로 이 계정 잔액을 삭제하시겠습니까? + + libs/ui/src/lib/account-balances/account-balances.component.ts + 120 + + + + Export Activities + 수출 활동 + + libs/ui/src/lib/activities-table/activities-table.component.html + 41 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 411 + + + + Export Drafts as ICS + 초안을 달력 파일로 내보내기 + + libs/ui/src/lib/activities-table/activities-table.component.html + 54 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 424 + + + + Draft + 초안 + + libs/ui/src/lib/activities-table/activities-table.component.html + 144 + + + + Clone + 클론 + + libs/ui/src/lib/activities-table/activities-table.component.html + 463 + + + + Export Draft as ICS + 초안을 달력 파일로 내보내기 + + libs/ui/src/lib/activities-table/activities-table.component.html + 473 + + + + Do you really want to delete this activity? + 정말로 이 활동을 삭제하시겠습니까? + + libs/ui/src/lib/activities-table/activities-table.component.ts + 288 + + + + Asset Profiles + 자산 프로필 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 106 + + + libs/ui/src/lib/assistant/assistant.html + 140 + + + + 50-Day Trend + 50일 추세 + + libs/ui/src/lib/benchmark/benchmark.component.html + 32 + + + + 200-Day Trend + 200일 추세 + + libs/ui/src/lib/benchmark/benchmark.component.html + 61 + + + + , + , + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 145 + + + + Last All Time High + 마지막 역대 최고치 + + libs/ui/src/lib/benchmark/benchmark.component.html + 90 + + + + Change from All Time High + 역대 최고치에서 변화 + + libs/ui/src/lib/benchmark/benchmark.component.html + 117 + + + + contact us + 저희에게 연락주세요 + + apps/client/src/app/pages/pricing/pricing-page.html + 339 + + + + from ATH + ATH에서 + + libs/ui/src/lib/benchmark/benchmark.component.html + 119 + + + + Market data provided by + 시장 데이터 제공: + + libs/ui/src/lib/data-provider-credits/data-provider-credits.component.html + 2 + + + + Savings Rate per Month + 월별 저축률 + + libs/ui/src/lib/fire-calculator/fire-calculator.component.html + 10 + + + + Annual Interest Rate + 연이자율 + + libs/ui/src/lib/fire-calculator/fire-calculator.component.html + 21 + + + + Retirement Date + 퇴직일 + + libs/ui/src/lib/fire-calculator/fire-calculator.component.html + 32 + + + + Projected Total Amount + 예상총액 + + libs/ui/src/lib/fire-calculator/fire-calculator.component.html + 59 + + + + Interest + 관심 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 69 + + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 352 + + + libs/ui/src/lib/fire-calculator/fire-calculator.component.ts + 387 + + + libs/ui/src/lib/i18n.ts + 40 + + + + Savings + 저금 + + libs/ui/src/lib/fire-calculator/fire-calculator.component.ts + 397 + + + + Allocation + 배당 + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 241 + + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 122 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 + + + + Show all + 모두 표시 + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 221 + + + + Account + 계정 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 85 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 315 + + + libs/ui/src/lib/i18n.ts + 4 + + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 4 + + + + Asia-Pacific + 아시아·태평양 + + libs/ui/src/lib/i18n.ts + 5 + + + + Asset Class + 자산 클래스 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 114 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 237 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 328 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 242 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 290 + + + libs/ui/src/lib/i18n.ts + 6 + + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + + + + Asset Sub Class + 자산 하위 클래스 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 123 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 246 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 344 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 251 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 309 + + + libs/ui/src/lib/i18n.ts + 7 + + + + Core + 핵심 + + libs/ui/src/lib/i18n.ts + 10 + + + + Switch to Ghostfolio Premium or Ghostfolio Open Source easily + Ghostfolio 프리미엄 또는 Ghostfolio Open Source로 쉽게 전환하세요 + + libs/ui/src/lib/i18n.ts + 12 + + + + Switch to Ghostfolio Premium easily + Ghostfolio 프리미엄으로 쉽게 전환하세요 + + libs/ui/src/lib/i18n.ts + 13 + + + + Switch to Ghostfolio Open Source or Ghostfolio Basic easily + Ghostfolio Open Source 또는 Ghostfolio Basic으로 쉽게 전환하세요 + + libs/ui/src/lib/i18n.ts + 14 + + + + Emergency Fund + 비상자금 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 164 + + + apps/client/src/app/pages/features/features-page.html + 89 + + + libs/ui/src/lib/i18n.ts + 16 + + + + Grant + 승인하다 + + libs/ui/src/lib/i18n.ts + 19 + + + + Higher Risk + 더 높은 위험 + + libs/ui/src/lib/i18n.ts + 20 + + + + This activity already exists. + 이 활동은 이미 존재합니다. + + libs/ui/src/lib/i18n.ts + 21 + + + + Japan + 일본 + + libs/ui/src/lib/i18n.ts + 92 + + + + Lower Risk + 위험 감소 + + libs/ui/src/lib/i18n.ts + 22 + + + + Month + + + libs/ui/src/lib/i18n.ts + 23 + + + + Months + 개월 + + libs/ui/src/lib/i18n.ts + 24 + + + + Other + 다른 + + libs/ui/src/lib/i18n.ts + 25 + + + libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts + 437 + + + + Preset + 프리셋 + + libs/ui/src/lib/i18n.ts + 27 + + + + Retirement Provision + 퇴직금 + + libs/ui/src/lib/i18n.ts + 28 + + + + Satellite + 위성 + + libs/ui/src/lib/i18n.ts + 29 + + + + Symbol + 상징 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 68 + + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 74 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 168 + + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 37 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 315 + + + libs/ui/src/lib/i18n.ts + 30 + + + + Tag + 꼬리표 + + libs/ui/src/lib/i18n.ts + 31 + + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + + + + Year + 년도 + + libs/ui/src/lib/i18n.ts + 32 + + + + View Details + 세부정보 보기 + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + + libs/ui/src/lib/accounts-table/accounts-table.component.html + 307 + + + + Years + 연령 + + libs/ui/src/lib/i18n.ts + 33 + + + + Sign in with OpenID Connect + OpenID Connect로 로그인 + + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html + 55 + + + + Buy + 구입하다 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 31 + + + libs/ui/src/lib/i18n.ts + 37 + + + + Fee + 요금 + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 262 + + + libs/ui/src/lib/activities-table/activities-table.component.html + 241 + + + libs/ui/src/lib/i18n.ts + 39 + + + + Valuable + 귀중한 + + libs/ui/src/lib/i18n.ts + 43 + + + + Liability + 책임 + + libs/ui/src/lib/i18n.ts + 41 + + + + Sell + 팔다 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 44 + + + libs/ui/src/lib/i18n.ts + 42 + + + + Cash + 현금 + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 212 + + + libs/ui/src/lib/i18n.ts + 55 + + + + Commodity + 상품 + + libs/ui/src/lib/i18n.ts + 47 + + + + Equity + 주식 + + apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html + 57 + + + libs/ui/src/lib/i18n.ts + 48 + + + + Fixed Income + 채권 + + libs/ui/src/lib/i18n.ts + 49 + + + + Real Estate + 부동산 + + libs/ui/src/lib/i18n.ts + 51 + + + + Authentication + 입증 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 35 + + + + Bond + 노예 + + libs/ui/src/lib/i18n.ts + 54 + + + + Cryptocurrency + 암호화폐 + + libs/ui/src/lib/i18n.ts + 57 + + + + ETF + ETF + + libs/ui/src/lib/i18n.ts + 58 + + + + Mutual Fund + 뮤추얼 펀드 + + libs/ui/src/lib/i18n.ts + 59 + + + + Precious Metal + 귀금속 + + libs/ui/src/lib/i18n.ts + 60 + + + + Private Equity + 사모펀드 + + libs/ui/src/lib/i18n.ts + 61 + + + + Stock + 재고 + + libs/ui/src/lib/i18n.ts + 62 + + + + Africa + 아프리카 + + libs/ui/src/lib/i18n.ts + 69 + + + + Asia + 아시아 + + libs/ui/src/lib/i18n.ts + 70 + + + + Europe + 유럽 + + libs/ui/src/lib/i18n.ts + 71 + + + + North America + 북미 + + libs/ui/src/lib/i18n.ts + 72 + + + + If you retire today, you would be able to withdraw + 오늘 퇴사하면 탈퇴 가능 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 68 + + + + Oceania + 오세아니아 + + libs/ui/src/lib/i18n.ts + 73 + + + + South America + 남아메리카 + + libs/ui/src/lib/i18n.ts + 74 + + + + Extreme Fear + 극심한 공포 + + libs/ui/src/lib/i18n.ts + 106 + + + + Extreme Greed + 극도의 탐욕 + + libs/ui/src/lib/i18n.ts + 107 + + + + Neutral + 중립적 + + libs/ui/src/lib/i18n.ts + 110 + + + + Valid until + 유효기간 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 74 + + + libs/ui/src/lib/membership-card/membership-card.component.html + 45 + + + + Time to add your first activity. + 첫 번째 활동을 추가할 시간입니다. + + libs/ui/src/lib/no-transactions-info/no-transactions-info.component.html + 12 + + + + No data available + 사용 가능한 데이터가 없습니다. + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 250 + + + apps/client/src/app/pages/public/public-page.html + 196 + + + libs/ui/src/lib/benchmark/benchmark.component.html + 209 + + + libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts + 439 + + + libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts + 452 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 181 + + + + If a translation is missing, kindly support us in extending it here. + 번역이 누락된 경우 여기에서 번역을 확장할 수 있도록 지원해 주시기 바랍니다. + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 59 + + + + Date Range + 기간 + + libs/ui/src/lib/assistant/assistant.html + 170 + + + + The current market price is + 현재 시장가격은 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 672 + + + + Test + 시험 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 511 + + + + Oops! Could not grant access. + 이런! 액세스 권한을 부여할 수 없습니다. + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts + 141 + + + + Argentina + 아르헨티나 + + libs/ui/src/lib/i18n.ts + 78 + + + + Restricted view + 제한된 보기 + + apps/client/src/app/components/access-table/access-table.component.html + 26 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 40 + + + + Permission + 허가 + + apps/client/src/app/components/access-table/access-table.component.html + 18 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 38 + + + + Private + 사적인 + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 30 + + + + Job Queue + 작업 대기열 + + libs/common/src/lib/routes/routes.ts + 46 + + + + Market data is delayed for + 시장 데이터가 지연됩니다. + + apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts + 95 + + + + Absolute Currency Performance + 절대적인 통화 성과 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 145 + + + + Close Holding + 닫기 보유 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 442 + + + + Absolute Asset Performance + 절대자산성과 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 102 + + + + Investment + 투자 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 171 + + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 60 + + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 80 + + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 96 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 88 + + + + here + 여기 + + apps/client/src/app/pages/pricing/pricing-page.html + 350 + + + + Asset Performance + 자산 성과 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 124 + + + + Currency Performance + 통화 성과 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 170 + + + + Year to date + 연초 현재 + + libs/ui/src/lib/assistant/assistant.component.ts + 377 + + + + Week to date + 이번주 현재까지 + + libs/ui/src/lib/assistant/assistant.component.ts + 369 + + + + Month to date + 월간 누계 + + libs/ui/src/lib/assistant/assistant.component.ts + 373 + + + + MTD + MTD + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 200 + + + libs/ui/src/lib/assistant/assistant.component.ts + 373 + + + + WTD + WTD + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 196 + + + libs/ui/src/lib/assistant/assistant.component.ts + 369 + + + + Oops! A data provider is experiencing the hiccups. + 이런! 데이터 제공업체에 문제가 발생했습니다. + + apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html + 8 + + + + View + 보다 + + apps/client/src/app/components/access-table/access-table.component.html + 23 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 42 + + + + Reset Filters + 필터 재설정 + + libs/ui/src/lib/assistant/assistant.html + 204 + + + + year + 년도 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 208 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 290 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 296 + + + libs/ui/src/lib/assistant/assistant.component.ts + 387 + + + + years + 연령 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 212 + + + libs/ui/src/lib/assistant/assistant.component.ts + 411 + + + + Apply Filters + 필터 적용 + + libs/ui/src/lib/assistant/assistant.html + 217 + + + + self-hosting + self-hosting + kebab-case + + libs/common/src/lib/routes/routes.ts + 243 + + + libs/common/src/lib/routes/routes.ts + 246 + + + + Self-Hosting + 셀프 호스팅 + + apps/client/src/app/pages/faq/faq-page.component.ts + 60 + + + libs/common/src/lib/routes/routes.ts + 248 + + + + Data Gathering + 데이터 수집 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 604 + + + apps/client/src/app/components/admin-overview/admin-overview.html + 60 + + + + General + 일반적인 + + apps/client/src/app/pages/faq/faq-page.component.ts + 49 + + + + Cloud + 구름 + + apps/client/src/app/pages/faq/faq-page.component.ts + 54 + + + libs/common/src/lib/routes/routes.ts + 240 + + + + Oops! It looks like you’re making too many requests. Please slow down a bit. + 이런! 요청을 너무 많이 하시는 것 같습니다. 조금 천천히 해주세요. + + apps/client/src/app/core/http-response.interceptor.ts + 106 + + + + My Account + 내 계정 + + apps/client/src/app/pages/i18n/i18n-page.html + 13 + + + + Closed + 닫은 + + apps/client/src/app/components/home-holdings/home-holdings.component.ts + 65 + + + + Active + 활동적인 + + apps/client/src/app/components/home-holdings/home-holdings.component.ts + 64 + + + + Indonesia + 인도네시아 공화국 + + libs/ui/src/lib/i18n.ts + 90 + + + + Activity + 활동 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 229 + + + + Dividend Yield + 배당수익률 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 196 + + + + Execute Job + 작업 실행 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 220 + + + + This action is not allowed. + 이 작업은 허용되지 않습니다. + + apps/client/src/app/core/http-response.interceptor.ts + 67 + + + + Priority + 우선 사항 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 96 + + + + Liquidity + 유동성 + + libs/ui/src/lib/i18n.ts + 50 + + + + Buy and sell + 구매 및 판매 + + libs/ui/src/lib/i18n.ts + 8 + + + + {VAR_PLURAL, plural, =1 {activity} other {activities}} + {VAR_PLURAL, 복수형, =1 {활동} 기타 {활동}} + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html + 14 + + + + Delete Activities + 활동 삭제 + + libs/ui/src/lib/activities-table/activities-table.component.html + 69 + + + + Internationalization + 국제화 + + libs/common/src/lib/routes/routes.ts + 119 + + + + Close Account + 계정 폐쇄 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 307 + + + + Do you really want to close your Ghostfolio account? + 정말로 Ghostfolio 계정을 폐쇄하시겠습니까? + + apps/client/src/app/components/user-account-settings/user-account-settings.component.ts + 207 + + + + Danger Zone + 위험지대 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 272 + + + + Approximation based on the top holdings of each ETF + 각 ETF의 상위 보유량을 기준으로 한 근사치 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 340 + + + + By ETF Holding + ETF 홀딩으로 + + apps/client/src/app/pages/portfolio/allocations/allocations-page.html + 333 + + + + Join now or check out the example account + 지금 가입하거나 예시 계정을 확인하세요. + + apps/client/src/app/pages/landing/landing-page.html + 333 + + + + Include in + 포함 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 376 + + + + Oops! There was an error setting up biometric authentication. + 이런! 생체 인증을 설정하는 중에 오류가 발생했습니다. + + apps/client/src/app/components/user-account-settings/user-account-settings.component.ts + 335 + + + + Show more + 더 보기 + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 174 + + + + Do you really want to delete these profiles? + 이 프로필을 정말로 삭제하시겠습니까? + + apps/client/src/app/components/admin-market-data/admin-market-data.service.ts + 68 + + + + Delete Profiles + 프로필 삭제 + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 242 + + + + Oops! Could not delete profiles. + 이런! 프로필을 삭제할 수 없습니다. + + apps/client/src/app/components/admin-market-data/admin-market-data.service.ts + 56 + + + + Benchmarks + 벤치마크 + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 127 + + + + Chart + 차트 + + apps/client/src/app/components/home-holdings/home-holdings.html + 19 + + + + Table + 테이블 + + apps/client/src/app/components/home-holdings/home-holdings.html + 16 + + + + Would you like to refine your personal investment strategy? + 개인 투자 전략개선하시겠습니까? + + apps/client/src/app/pages/public/public-page.html + 234 + + + + Wealth + 재산 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 98 + + + + Community + 지역 사회 + + apps/client/src/app/components/footer/footer.component.html + 80 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 85 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 90 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 94 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 98 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 102 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 106 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 110 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 114 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 118 + + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 123 + + + apps/client/src/app/pages/features/features-page.html + 276 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 85 + + + + Thailand + 태국 + + libs/ui/src/lib/i18n.ts + 100 + + + + India + 인도 + + libs/ui/src/lib/i18n.ts + 89 + + + + Austria + 오스트리아 + + libs/ui/src/lib/i18n.ts + 80 + + + + Poland + 폴란드 + + libs/ui/src/lib/i18n.ts + 95 + + + + Italy + 이탈리아 + + libs/ui/src/lib/i18n.ts + 91 + + + + User Experience + 사용자 경험 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 97 + + + + App + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 83 + + + + Tool + 도구 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 96 + + + + Investor + 투자자 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 89 + + + + Wealth Management + 자산관리 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 99 + + + + View Holding + 보유보기 + + libs/ui/src/lib/activities-table/activities-table.component.html + 450 + + + + Canada + 캐나다 + + libs/ui/src/lib/i18n.ts + 84 + + + + New Zealand + 뉴질랜드 + + libs/ui/src/lib/i18n.ts + 94 + + + + Netherlands + 네덜란드 + + libs/ui/src/lib/i18n.ts + 93 + + + + Alternative + 대안 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 82 + + + + Family Office + 패밀리오피스 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 86 + + + + Personal Finance + 개인 금융 + + apps/client/src/app/components/footer/footer.component.html + 7 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 92 + + + + Software + 소프트웨어 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 95 + + + + Romania + 루마니아 + + libs/ui/src/lib/i18n.ts + 96 + + + + Germany + 독일 + + libs/ui/src/lib/i18n.ts + 88 + + + + United States + 미국 + + libs/ui/src/lib/i18n.ts + 103 + + + + Budgeting + 예산 편성 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 84 + + + + Belgium + 벨기에 + + libs/ui/src/lib/i18n.ts + 81 + + + + Open Source + 오픈 소스 + + apps/client/src/app/pages/landing/landing-page.html + 159 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 90 + + + + Czech Republic + 체코 + + libs/ui/src/lib/i18n.ts + 85 + + + + Australia + 호주 + + libs/ui/src/lib/i18n.ts + 79 + + + + South Africa + 남아프리카 + + libs/ui/src/lib/i18n.ts + 98 + + + + Bulgaria + 불가리아 + + libs/ui/src/lib/i18n.ts + 83 + + + + Privacy + 은둔 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts + 93 + + + + Finland + 핀란드 + + libs/ui/src/lib/i18n.ts + 86 + + + + France + 프랑스 + + libs/ui/src/lib/i18n.ts + 87 + + + + Error + 오류 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 663 + + + + Cancel + 취소 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 161 + + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 609 + + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 57 + + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 44 + + + apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.html + 27 + + + apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html + 17 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 66 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 105 + + + apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.html + 65 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 345 + + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 48 + + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html + 46 + + + libs/ui/src/lib/i18n.ts + 9 + + + + Role + 역할 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 14 + + + + Yes + + + libs/ui/src/lib/i18n.ts + 34 + + + + , based on your total assets of + , 귀하의 총 자산을 기준으로 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 96 + + + + Inactive + 비활성 + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 + + + + Close + 닫다 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 611 + + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 59 + + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 46 + + + apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.html + 29 + + + apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html + 19 + + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 130 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 68 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 107 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 347 + + + libs/ui/src/lib/i18n.ts + 11 + + + + Activate + 활성화 + + apps/client/src/app/components/rule/rule.component.html + 83 + + + + Oops! Could not update access. + 이런! 액세스를 업데이트할 수 없습니다. + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts + 178 + + + + Deactivate + 비활성화 + + apps/client/src/app/components/rule/rule.component.html + 78 + + + + Threshold Max + 임계값 최대 + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 93 + + + + send an e-mail to + 에게 이메일을 보내다 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 87 + + + + Customize + 사용자 정의 + + apps/client/src/app/components/rule/rule.component.html + 69 + + + + Portfolio Snapshot + 포트폴리오 스냅샷 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 56 + + + + Threshold Min + 임계값 최소 + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 55 + + + + If you plan to open an account at + 에서 계좌를 개설할 계획이라면 + + apps/client/src/app/pages/pricing/pricing-page.html + 315 + + + + Performance with currency effect Performance + 환율 효과가 있는 실적 실적 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 83 + + + + Copy link to clipboard + 링크를 클립보드에 복사 + + apps/client/src/app/components/access-table/access-table.component.html + 84 + + + + Change with currency effect Change + 통화 효과로 변경 변경 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 63 + + + + From the beginning + 처음부터 + + apps/client/src/app/pages/public/public-page.html + 60 + + + + This year + 올해 + + apps/client/src/app/pages/public/public-page.html + 42 + + + + offers a free plan + 은(는) 무료 요금제를 제공합니다 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 256 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 273 + + + + does not offer a free plan + 은(는) 무료 요금제를 제공하지 않습니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 263 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 280 + + + + Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. + Ghostfolio는 개인이 주식, ETF 또는 암호화폐를 추적하고 확실한 데이터 기반 투자 결정을 내릴 수 있는 경량 자산 관리 애플리케이션입니다. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 10 + + + + , assuming a + , 가정 + + apps/client/src/app/pages/portfolio/fire/fire-page.html + 174 + + + + to use our referral link and get a Ghostfolio Premium membership for one year + 추천 링크를 사용하고 1년 동안 Ghostfolio 프리미엄 멤버십을 얻으려면 + + apps/client/src/app/pages/pricing/pricing-page.html + 343 + + + + can be self-hosted + 은(는) 자체 호스팅 가능 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 178 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 195 + + + + cannot be self-hosted + 은(는) 자체 호스팅할 수 없습니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 185 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 202 + + + + can be used anonymously + 은(는) 익명으로 사용할 수 있습니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 217 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 234 + + + + cannot be used anonymously + 은(는) 익명으로 사용할 수 없습니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 224 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 241 + + + + is not Open Source Software + 은(는) 오픈 소스 소프트웨어가 아닙니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 146 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 163 + + + + is Open Source Software + 은(는) 오픈 소스 소프트웨어입니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 139 + + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 156 + + + + This page has been archived. + 이 페이지는 보관되었습니다. + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 14 + + + + Oops! Invalid currency. + 이런! 통화가 잘못되었습니다. + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 48 + + + + Oops! Could not find any assets. + 이런! 자산을 찾을 수 없습니다. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 40 + + + + Ukraine + 우크라이나 + + libs/ui/src/lib/i18n.ts + 101 + + + + Set API key + API 키 설정 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 171 + + + + Get access to 80’000+ tickers from over 50 exchanges + 50개 이상의 거래소에서 80,000개 이상의 티커에 접근하세요 + + libs/ui/src/lib/i18n.ts + 26 + + + + Data Providers + 데이터 제공자 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 4 + + + + Join now + 지금 가입하세요 + + apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html + 193 + + + + Glossary + 용어집 + + apps/client/src/app/pages/resources/glossary/resources-glossary.component.html + 4 + + + apps/client/src/app/pages/resources/resources-page.component.ts + 45 + + + libs/common/src/lib/routes/routes.ts + 293 + + + + glossary + glossary + kebab-case + + libs/common/src/lib/routes/routes.ts + 288 + + + libs/common/src/lib/routes/routes.ts + 291 + + + + Guides + 가이드 + + apps/client/src/app/pages/resources/guides/resources-guides.component.html + 4 + + + apps/client/src/app/pages/resources/resources-page.component.ts + 34 + + + libs/common/src/lib/routes/routes.ts + 301 + + + + guides + guides + kebab-case + + libs/common/src/lib/routes/routes.ts + 296 + + + libs/common/src/lib/routes/routes.ts + 299 + + + + Threshold range + 임계값 범위 + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray는 정적 분석을 사용하여 포트폴리오의 잠재적인 문제와 위험을 찾아냅니다. 아래 규칙을 조정하고 개인 투자 전략에 맞게 맞춤 임계값을 설정하세요. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 6 + + + + Please enter your Ghostfolio API key: + Ghostfolio API 키를 입력하세요: + + apps/client/src/app/pages/api/api-page.component.ts + 43 + + + + of + ~의 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 135 + + + + Do you really want to delete the API key? + API 키를 정말로 삭제하시겠습니까? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 128 + + + + Remove API key + API 키 제거 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 161 + + + + daily requests + 일일 요청 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 137 + + + + Generate Ghostfolio Premium Data Provider API key for self-hosted environments... + 자체 호스팅 환경을 위한 Ghostfolio 프리미엄 데이터 공급자 API 키 생성... + + libs/ui/src/lib/membership-card/membership-card.component.html + 29 + + + + API Key + API 키 + + libs/ui/src/lib/membership-card/membership-card.component.html + 21 + + + + API Requests Today + 오늘의 API 요청 + + apps/client/src/app/components/admin-users/admin-users.html + 161 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 98 + + + + Could not generate an API key + API 키를 생성할 수 없습니다. + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 144 + + + + Do you really want to generate a new API key? + 정말로 새 API 키를 생성하시겠습니까? + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 167 + + + + Ghostfolio Premium Data Provider API Key + Ghostfolio 프리미엄 데이터 공급자 API 키 + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 162 + + + + Set this API key in your self-hosted environment: + 자체 호스팅 환경에서 이 API 키를 설정하세요. + + apps/client/src/app/components/user-account-membership/user-account-membership.component.ts + 159 + + + + rules align with your portfolio. + 규칙은 귀하의 포트폴리오와 일치합니다. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 58 + + + + out of + 밖으로 + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 56 + + + + Save + 구하다 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 620 + + + apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html + 68 + + + apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html + 55 + + + apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.html + 38 + + + apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html + 28 + + + apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts + 106 + + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 136 + + + apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html + 81 + + + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html + 116 + + + apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html + 356 + + + libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html + 48 + + + + Received Access + 수신된 액세스 + + apps/client/src/app/components/user-account-access/user-account-access.html + 53 + + + + Check the system status at + 시스템 상태를 확인하세요. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 57 + + + + Me + + + apps/client/src/app/components/header/header.component.html + 213 + + + apps/client/src/app/components/user-account-access/user-account-access.component.ts + 260 + + + + Please enter your Ghostfolio API key. + Ghostfolio API 키를 입력하세요. + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 147 + + + + AI prompt has been copied to the clipboard + AI 프롬프트가 클립보드에 복사되었습니다. + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 199 + + + + Link has been copied to the clipboard + 링크가 클립보드에 복사되었습니다. + + apps/client/src/app/components/access-table/access-table.component.ts + 101 + + + + Mode + 방법 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 461 + + + + Default Market Price + 기본 시장 가격 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 420 + + + + Selector + 선택자 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 477 + + + + Instant + 즉각적인 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 234 + + + + Lazy + 게으른 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 230 + + + + HTTP Request Headers + HTTP 요청 헤더 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 433 + + + + real-time + 실시간 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 234 + + + + end of day + 하루의 끝 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 230 + + + + Open Duck.ai + 오픈 Duck.ai + + apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts + 200 + + + + Create + 만들다 + + libs/ui/src/lib/tags-selector/tags-selector.component.html + 50 + + + + Change + 변화 + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 143 + + + libs/ui/src/lib/treemap-chart/treemap-chart.component.ts + 368 + + + + Performance + 성능 + + apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html + 6 + + + apps/client/src/app/components/home-overview/home-overview.component.ts + 55 + + + libs/ui/src/lib/holdings-table/holdings-table.component.html + 166 + + + libs/ui/src/lib/treemap-chart/treemap-chart.component.ts + 368 + + + libs/ui/src/lib/treemap-chart/treemap-chart.component.ts + 381 + + + + The project has been initiated by + 프로젝트는 다음에 의해 시작되었습니다. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 40 + + + + Copy AI prompt to clipboard for analysis + 분석을 위해 AI 프롬프트를 클립보드에 복사 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 67 + + + + Singapore + 싱가포르 + + libs/ui/src/lib/i18n.ts + 97 + + + + Armenia + 아르메니아 + + libs/ui/src/lib/i18n.ts + 77 + + + + British Virgin Islands + 영국령 버진아일랜드 + + libs/ui/src/lib/i18n.ts + 82 + + + + Copy portfolio data to clipboard for AI prompt + AI 프롬프트를 위해 포트폴리오 데이터를 클립보드에 복사 + + apps/client/src/app/pages/portfolio/analysis/analysis-page.html + 42 + + + + I understand that if I lose my security token, I cannot recover my account + 보안 토큰을 분실하면 계정을 복구할 수 없다는 점을 이해합니다. + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 28 + + + + Please keep your security token safe. If you lose it, you will not be able to recover your account. + 보안 토큰을 안전하게 보관하세요. 분실한 경우 계정을 복구할 수 없습니다. + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 18 + + + + Here is your security token. It is only visible once, please store and keep it in a safe place. + 여기 보안 토큰이 있습니다. 한 번만 볼 수 있으므로 안전한 곳에 보관하여 보관하시기 바랍니다. + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 67 + + + + Continue + 계속하다 + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 57 + + + + Terms and Conditions + 이용약관 + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 15 + + + + Do you really want to generate a new security token for this user? + 정말로 이 사용자에 대한 새 보안 토큰을 생성하시겠습니까? + + apps/client/src/app/components/admin-users/admin-users.component.ts + 236 + + + + Find account, holding or page... + 계정, 보유 또는 페이지 찾기... + + libs/ui/src/lib/assistant/assistant.component.ts + 153 + + + + Security token + 보안 토큰 + + apps/client/src/app/components/admin-users/admin-users.component.ts + 231 + + + apps/client/src/app/components/user-account-access/user-account-access.component.ts + 170 + + + + Generate Security Token + 보안 토큰 생성 + + apps/client/src/app/components/admin-users/admin-users.html + 243 + + + + United Kingdom + 영국 + + libs/ui/src/lib/i18n.ts + 102 + + + + Terms of Service + 이용약관 + + apps/client/src/app/components/footer/footer.component.html + 62 + + + libs/common/src/lib/routes/routes.ts + 217 + + + + terms-of-service + terms-of-service + kebab-case + + libs/common/src/lib/routes/routes.ts + 212 + + + libs/common/src/lib/routes/routes.ts + 215 + + + + Terms of Service + 이용약관 + + apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.html + 5 + + + + and I agree to the Terms of Service. + 서비스 약관에 동의합니다. + + apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html + 34 + + + + () is already in use. + ()은(는) 이미 사용 중입니다. + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 599 + + + + An error occurred while updating to (). + ()로 업데이트하는 동안 오류가 발생했습니다. + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 607 + + + + Apply + 적용하다 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 153 + + + + with API access for + 다음에 대한 API 액세스 권한이 있는 + + apps/client/src/app/pages/pricing/pricing-page.html + 238 + + + + Data Gathering is off + 데이터 수집이 사용 중지되었습니다. + + apps/client/src/app/components/admin-market-data/admin-market-data.html + 38 + + + + Performance Calculation + 성능 계산 + + apps/client/src/app/components/user-account-settings/user-account-settings.html + 31 + + + + someone + 누구 + + apps/client/src/app/pages/public/public-page.component.ts + 59 + + + + Add asset to watchlist + 관심 목록에 자산 추가 + + apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html + 7 + + + + Watchlist + 관심 목록 + + apps/client/src/app/components/home-watchlist/home-watchlist.html + 4 + + + apps/client/src/app/pages/features/features-page.html + 197 + + + libs/common/src/lib/routes/routes.ts + 110 + + + + Do you really want to delete this item? + 이 항목을 정말로 삭제하시겠습니까? + + libs/ui/src/lib/benchmark/benchmark.component.ts + 139 + + + + Log out + 로그아웃 + + apps/client/src/app/components/header/header.component.html + 329 + + + + Calculations are based on delayed market data and may not be displayed in real-time. + 계산은 지연된 시장 데이터를 기반으로 하며 실시간으로 표시되지 않을 수 있습니다. + + apps/client/src/app/components/home-market/home-market.html + 45 + + + apps/client/src/app/components/markets/markets.html + 54 + + + + changelog + changelog + kebab-case + + libs/common/src/lib/routes/routes.ts + 180 + + + libs/common/src/lib/routes/routes.ts + 183 + + + + Sync Demo User Account + 데모 사용자 계정 동기화 + + apps/client/src/app/components/admin-overview/admin-overview.html + 195 + + + + Demo user account has been synced. + 데모 사용자 계정이 동기화되었습니다. + + apps/client/src/app/components/admin-overview/admin-overview.component.ts + 275 + + + + Set up + 설정 + + apps/client/src/app/pages/i18n/i18n-page.html + 145 + + + + No emergency fund has been set up + 비상금은 마련되지 않았습니다 + + apps/client/src/app/pages/i18n/i18n-page.html + 147 + + + + An emergency fund has been set up + 비상금이 마련됐어요 + + apps/client/src/app/pages/i18n/i18n-page.html + 150 + + + + Fee Ratio + 수수료 비율 + + apps/client/src/app/pages/i18n/i18n-page.html + 152 + + + + The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + 수수료가 초기 투자금(${feeRatio}%)의 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 154 + + + + The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + 수수료는 초기 투자금의 ${thresholdMax}%(${feeRatio}%)를 초과하지 않습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 158 + + + + Quick Links + 빠른 링크 + + libs/ui/src/lib/assistant/assistant.html + 58 + + + + Live Demo + 라이브 데모 + + apps/client/src/app/pages/landing/landing-page.html + 48 + + + apps/client/src/app/pages/landing/landing-page.html + 349 + + + libs/common/src/lib/routes/routes.ts + 231 + + + + Open Source Alternative to + 오픈 소스 대안 + + libs/common/src/lib/routes/routes.ts + 326 + + + + Single Account + 단일 계정 + + apps/client/src/app/pages/i18n/i18n-page.html + 28 + + + + Your net worth is managed by a single account + 귀하의 순자산은 단일 계정으로 관리됩니다 + + apps/client/src/app/pages/i18n/i18n-page.html + 30 + + + + Your net worth is managed by ${accountsLength} accounts + 귀하의 순자산은 ${accountsLength} 계정에서 관리됩니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 36 + + + + personal-finance-tools + personal-finance-tools + kebab-case + + libs/common/src/lib/routes/routes.ts + 312 + + + libs/common/src/lib/routes/routes.ts + 315 + + + libs/common/src/lib/routes/routes.ts + 323 + + + + markets + markets + kebab-case + + libs/common/src/lib/routes/routes.ts + 304 + + + libs/common/src/lib/routes/routes.ts + 307 + + + + Get Access + 액세스 권한 얻기 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 27 + + + + Fuel your self-hosted Ghostfolio with a powerful data provider to access 80,000+ tickers from over 50 exchanges worldwide. + 자체 호스팅 Ghostfolio강력한 데이터 제공업체와 함께 활용하여 전 세계 50개 이상의 거래소에서 80,000개 이상의 시세에 액세스하세요. + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 16 + + + + Learn more + 자세히 알아보기 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Limited Offer! + 한정 상품! + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 41 + + + apps/client/src/app/pages/pricing/pricing-page.html + 297 + + + + Get extra + 추가 구매 + + apps/client/src/app/components/user-account-membership/user-account-membership.html + 44 + + + apps/client/src/app/pages/pricing/pricing-page.html + 300 + + + + Unavailable + 없는 + + apps/client/src/app/components/data-provider-status/data-provider-status.component.html + 5 + + + + Available + 사용 가능 + + apps/client/src/app/components/data-provider-status/data-provider-status.component.html + 3 + + + + Current month + 이번 달 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts + 200 + + + + new + 새로운 + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 67 + + + apps/client/src/app/pages/admin/admin-page.component.ts + 56 + + + + Investment + 투자 + + apps/client/src/app/pages/i18n/i18n-page.html + 15 + + + + Over ${thresholdMax}% of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) + 현재 투자의 ${thresholdMax}% 이상이 ${maxAccountName} (${maxInvestmentRatio}%)에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 17 + + + + The major part of your current investment is at ${maxAccountName} (${maxInvestmentRatio}%) and does not exceed ${thresholdMax}% + 현재 투자의 주요 부분은 ${maxAccountName} (${maxInvestmentRatio}%)이며 ${thresholdMax}%를 초과하지 않습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 24 + + + + Equity + 주식 + + apps/client/src/app/pages/i18n/i18n-page.html + 41 + + + + The equity contribution of your current investment (${equityValueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 지분 기여도(${equityValueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 43 + + + + The equity contribution of your current investment (${equityValueRatio}%) is below ${thresholdMin}% + 현재 투자의 지분 기여도(${equityValueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 47 + + + + The equity contribution of your current investment (${equityValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 지분 기여도(${equityValueRatio}%)가 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 51 + + + + Fixed Income + 채권 + + apps/client/src/app/pages/i18n/i18n-page.html + 55 + + + + The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 고정 수입 기여도(${fixedIncomeValueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 57 + + + + The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is below ${thresholdMin}% + 현재 투자의 고정 수입 기여도(${fixedIncomeValueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 61 + + + + The fixed income contribution of your current investment (${fixedIncomeValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 고정 수입 기여도(${fixedIncomeValueRatio}%)가 ${thresholdMin}% ~ ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 66 + + + + Investment: Base Currency + 투자: 기본 통화 + + apps/client/src/app/pages/i18n/i18n-page.html + 85 + + + + The major part of your current investment is not in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) + 현재 투자의 주요 부분이 기본 통화(${baseCurrency}의 ${baseCurrencyValueRatio}%)로 되어 있지 않습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 88 + + + + The major part of your current investment is in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) + 현재 투자의 주요 부분은 기본 통화(${baseCurrency}의 ${baseCurrencyValueRatio}%)입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 92 + + + + Investment + 투자 + + apps/client/src/app/pages/i18n/i18n-page.html + 95 + + + + Over ${thresholdMax}% of your current investment is in ${currency} (${maxValueRatio}%) + 현재 투자의 ${thresholdMax}% 이상이 ${currency}(${maxValueRatio}%)에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 97 + + + + The major part of your current investment is in ${currency} (${maxValueRatio}%) and does not exceed ${thresholdMax}% + 현재 투자의 주요 부분은 ${currency} (${maxValueRatio}%)이며 ${thresholdMax}%를 초과하지 않습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 101 + + + + start + start + kebab-case + + libs/common/src/lib/routes/routes.ts + 336 + + + libs/common/src/lib/routes/routes.ts + 337 + + + + Generate + 생성하다 + + apps/client/src/app/components/user-account-access/user-account-access.html + 43 + + + + 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_에 게시하세요. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 69 + + + + Do you really want to generate a new security token? + 정말로 새로운 보안 토큰을 생성하시겠습니까? + + apps/client/src/app/components/user-account-access/user-account-access.component.ts + 175 + + + + Cryptocurrencies + 암호화폐 + + apps/client/src/app/components/markets/markets.component.ts + 53 + + + apps/client/src/app/pages/features/features-page.html + 51 + + + + Stocks + 주식 + + apps/client/src/app/components/markets/markets.component.ts + 52 + + + apps/client/src/app/pages/features/features-page.html + 15 + + + + + + + apps/client/src/app/components/admin-users/admin-users.html + 39 + + + + Manage Asset Profile + 자산 프로필 관리 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 466 + + + + Alternative Investment + 대체투자 + + libs/ui/src/lib/i18n.ts + 46 + + + + Collectible + 소장용 + + libs/ui/src/lib/i18n.ts + 56 + + + + Average Unit Price + 평균단가 + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts + 113 + + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 101 + + + + No results found... + 검색된 결과가 없습니다... + + libs/ui/src/lib/assistant/assistant.html + 51 + + + + Account Cluster Risks + 계정 클러스터 위험 + + apps/client/src/app/pages/i18n/i18n-page.html + 14 + + + + Asset Class Cluster Risks + 자산 클래스 클러스터 위험 + + apps/client/src/app/pages/i18n/i18n-page.html + 39 + + + + Currency Cluster Risks + 통화 클러스터 위험 + + apps/client/src/app/pages/i18n/i18n-page.html + 83 + + + + Economic Market Cluster Risks + 경제 시장 클러스터 위험 + + apps/client/src/app/pages/i18n/i18n-page.html + 106 + + + + Emergency Fund + 비상자금 + + apps/client/src/app/pages/i18n/i18n-page.html + 144 + + + + Fees + 수수료 + + apps/client/src/app/pages/i18n/i18n-page.html + 161 + + + + Liquidity + 유동성 + + apps/client/src/app/pages/i18n/i18n-page.html + 70 + + + + Buying Power + 매수 가능 금액 + + apps/client/src/app/pages/i18n/i18n-page.html + 71 + + + + Your buying power is below ${thresholdMin} ${baseCurrency} + 귀하의 구매력은 ${thresholdMin} ${baseCurrency} 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 73 + + + + Your buying power is 0 ${baseCurrency} + 귀하의 구매력은 0입니다 ${baseCurrency} + + apps/client/src/app/pages/i18n/i18n-page.html + 77 + + + + Your buying power exceeds ${thresholdMin} ${baseCurrency} + 매수 가능 금액이 ${thresholdMin} ${baseCurrency}을(를) 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 80 + + + + Regional Market Cluster Risks + 지역 시장 클러스터 위험 + + apps/client/src/app/pages/i18n/i18n-page.html + 163 + + + + Developed Markets + 선진시장 + + apps/client/src/app/pages/i18n/i18n-page.html + 109 + + + + The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 선진국 시장 기여도(${개발된MarketsValueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 112 + + + + The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% + 현재 투자의 선진국 시장 기여도(${개발된MarketsValueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 117 + + + + The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 선진국 시장 기여도(${개발된MarketsValueRatio}%)는 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 122 + + + + Emerging Markets + 신흥시장 + + apps/client/src/app/pages/i18n/i18n-page.html + 127 + + + + The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 신흥 시장 기여도(${emergingMarketsValueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 130 + + + + The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% + 현재 투자의 신흥 시장 기여도(${emergingMarketsValueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 135 + + + + The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 신흥 시장 기여도(${emergingMarketsValueRatio}%)가 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 140 + + + + No accounts have been set up + 설정된 계정이 없습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 21 + + + + Your net worth is managed by 0 accounts + 귀하의 순자산은 0개의 계정에서 관리됩니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 33 + + + + Asia-Pacific + 아시아·태평양 + + apps/client/src/app/pages/i18n/i18n-page.html + 165 + + + + The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 아시아 태평양 시장 기여도(${valueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 167 + + + + The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + 현재 투자의 아시아 태평양 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 171 + + + + The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 아시아 태평양 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 175 + + + + Emerging Markets + 신흥시장 + + apps/client/src/app/pages/i18n/i18n-page.html + 180 + + + + The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 신흥 시장 기여도(${valueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 183 + + + + The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + 현재 투자의 신흥 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 187 + + + + The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 신흥 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 191 + + + + Europe + 유럽 + + apps/client/src/app/pages/i18n/i18n-page.html + 195 + + + + The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 유럽 시장 기여도(${valueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 197 + + + + The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + 현재 투자의 유럽 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 201 + + + + The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 유럽 시장 기여도(${valueRatio}%)는 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 205 + + + + Japan + 일본 + + apps/client/src/app/pages/i18n/i18n-page.html + 209 + + + + The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 일본 시장 기여도(${valueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 211 + + + + The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + 현재 투자의 일본 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 215 + + + + The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 일본 시장 기여도(${valueRatio}%)는 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 219 + + + + North America + 북미 + + apps/client/src/app/pages/i18n/i18n-page.html + 223 + + + + The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + 현재 투자의 북미 시장 기여도(${valueRatio}%)가 ${thresholdMax}%를 초과합니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 225 + + + + The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + 현재 투자의 북미 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 미만입니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 229 + + + + The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + 현재 투자의 북미 시장 기여도(${valueRatio}%)가 ${thresholdMin}% 및 ${thresholdMax}% 범위 내에 있습니다. + + apps/client/src/app/pages/i18n/i18n-page.html + 233 + + + + Support Ghostfolio + 고스트폴리오 지원 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 166 + + + + Find Ghostfolio on GitHub + 깃허브에서 Ghostfolio 찾기 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 99 + + + apps/client/src/app/pages/about/overview/about-overview-page.html + 138 + + + + Ghostfolio is an independent & bootstrapped business + Ghostfolio는 독립적이고 부트스트랩된 사업입니다. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 157 + + + + Send an e-mail + 이메일 보내기 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 89 + + + apps/client/src/app/pages/about/overview/about-overview-page.html + 128 + + + + Registration Date + 등록일 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 26 + + + + Join the Ghostfolio Slack community + Ghostfolio 슬랙 커뮤니티에 참여하세요 + + apps/client/src/app/pages/about/overview/about-overview-page.html + 109 + + + + Follow Ghostfolio on LinkedIn + LinkedIn에서 Ghostfolio를 팔로우하세요. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 147 + + + + Follow Ghostfolio on X (formerly Twitter) + X(이전의 Twitter)에서 Ghostfolio를 팔로우하세요. + + apps/client/src/app/pages/about/overview/about-overview-page.html + 118 + + + + + diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 169ab80c1..a10a828e1 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -193,6 +193,7 @@ export const SUPPORTED_LANGUAGE_CODES = [ 'es', 'fr', 'it', + 'ko', 'nl', 'pl', 'pt', diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 9c1a0f104..cb4c0e1b7 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -11,7 +11,20 @@ import { parseISO, subDays } from 'date-fns'; -import { ca, de, es, fr, it, nl, pl, pt, tr, uk, zhCN } from 'date-fns/locale'; +import { + ca, + de, + es, + fr, + it, + ko, + nl, + pl, + pt, + tr, + uk, + zhCN +} from 'date-fns/locale'; import { get, isNil, isString } from 'lodash'; import { @@ -185,6 +198,8 @@ export function getDateFnsLocale(aLanguageCode: string) { return fr; } else if (aLanguageCode === 'it') { return it; + } else if (aLanguageCode === 'ko') { + return ko; } else if (aLanguageCode === 'nl') { return nl; } else if (aLanguageCode === 'pl') {