From 5c07992791deb5b29b0cea4fed2678b72693a7e3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:33:45 +0200 Subject: [PATCH 001/146] Task/improve changelog entry (#5790) * Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd841de15..e783ae690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Formatted the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) -- Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action of the analysis page (experimental) +- Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) - Improved the language localization for German (`de`) ## 2.209.0 - 2025-10-18 From e6ebe7e50105ec7f29a844f504f134453bece71a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 19 Oct 2025 20:34:38 +0200 Subject: [PATCH 002/146] Task/harmonize wording in glossary (#5781) * Harmonize wording --- .../resources/glossary/resources-glossary.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html index b028734a7..b65054bba 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html @@ -105,8 +105,8 @@

Personal Finance Tools

Personal finance tools are software applications that help - individuals manage their money, track expenses, set budgets, - monitor investments, and make informed financial decisions. + manage your money, track expenses, set budgets, monitor + investments, and make informed financial decisions.
Date: Mon, 20 Oct 2025 19:13:31 +0200 Subject: [PATCH 003/146] Task/improve typings of getInfo() functionality (#5803) * Improve typings --- apps/api/src/app/info/info.controller.ts | 4 ++-- apps/client/src/main.ts | 4 ++-- libs/common/src/lib/interfaces/index.ts | 2 ++ .../src/lib/interfaces/responses/info-response.interface.ts | 3 +++ 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 libs/common/src/lib/interfaces/responses/info-response.interface.ts diff --git a/apps/api/src/app/info/info.controller.ts b/apps/api/src/app/info/info.controller.ts index 67d4101a3..7011713dd 100644 --- a/apps/api/src/app/info/info.controller.ts +++ b/apps/api/src/app/info/info.controller.ts @@ -1,5 +1,5 @@ import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; -import { InfoItem } from '@ghostfolio/common/interfaces'; +import { InfoResponse } from '@ghostfolio/common/interfaces'; import { Controller, Get, UseInterceptors } from '@nestjs/common'; @@ -11,7 +11,7 @@ export class InfoController { @Get() @UseInterceptors(TransformDataSourceInResponseInterceptor) - public async getInfo(): Promise { + public async getInfo(): Promise { return this.infoService.get(); } } diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index 2f656ddf2..96d6c0582 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -1,5 +1,5 @@ import { locale } from '@ghostfolio/common/config'; -import { InfoItem } from '@ghostfolio/common/interfaces'; +import { InfoResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; import { enableProdMode } from '@angular/core'; @@ -11,7 +11,7 @@ import { environment } from './environments/environment'; (async () => { const response = await fetch('/api/v1/info'); - const info: InfoItem = await response.json(); + const info: InfoResponse = await response.json(); const utmSource = window.localStorage.getItem('utm_source') as | 'ios' | 'trusted-web-activity'; diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 578d9cec8..b69891e31 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -50,6 +50,7 @@ import type { DividendsResponse } from './responses/dividends-response.interface import type { ResponseError } from './responses/errors.interface'; import type { HistoricalResponse } from './responses/historical-response.interface'; import type { ImportResponse } from './responses/import-response.interface'; +import type { InfoResponse } from './responses/info-response.interface'; import type { LookupResponse } from './responses/lookup-response.interface'; import type { MarketDataDetailsResponse } from './responses/market-data-details-response.interface'; import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface'; @@ -112,6 +113,7 @@ export { HoldingWithParents, ImportResponse, InfoItem, + InfoResponse, InvestmentItem, LineChartItem, LookupItem, diff --git a/libs/common/src/lib/interfaces/responses/info-response.interface.ts b/libs/common/src/lib/interfaces/responses/info-response.interface.ts new file mode 100644 index 000000000..45e62db73 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/info-response.interface.ts @@ -0,0 +1,3 @@ +import { InfoItem } from '../index'; + +export interface InfoResponse extends InfoItem {} From be0ddd6298f9993f6a3ea0486f374a1b84b0c2f5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 20 Oct 2025 19:17:47 +0200 Subject: [PATCH 004/146] Task/harmonize interfaces naming (#5796) * Harmonize interfaces naming --- .../ghostfolio/ghostfolio.service.ts | 6 +-- .../exchange-rate/exchange-rate.controller.ts | 4 +- .../calculator/portfolio-calculator.ts | 4 +- .../interfaces/get-values-params.interface.ts | 4 +- apps/api/src/app/symbol/symbol.controller.ts | 4 +- apps/api/src/app/symbol/symbol.service.ts | 10 ++--- .../alpha-vantage/alpha-vantage.service.ts | 14 +++---- .../alpha-vantage/interfaces/interfaces.ts | 2 +- .../coingecko/coingecko.service.ts | 12 +++--- .../data-provider/data-provider.service.ts | 24 +++++------ .../eod-historical-data.service.ts | 14 +++---- .../financial-modeling-prep.service.ts | 14 +++---- .../ghostfolio/ghostfolio.service.ts | 14 +++---- .../google-sheets/google-sheets.service.ts | 12 +++--- .../interfaces/data-provider.interface.ts | 10 ++--- .../data-provider/manual/manual.service.ts | 12 +++--- .../rapid-api/interfaces/interfaces.ts | 2 +- .../rapid-api/rapid-api.service.ts | 8 ++-- .../yahoo-finance/yahoo-finance.service.ts | 14 +++---- .../exchange-rate-data.service.ts | 4 +- .../api/src/services/interfaces/interfaces.ts | 6 +-- .../market-data/market-data.service.ts | 4 +- .../data-gathering.processor.ts | 4 +- .../data-gathering/data-gathering.service.ts | 12 +++--- .../portfolio-snapshot-queue-job.interface.ts | 2 +- .../portfolio-snapshot.processor.ts | 6 +-- .../portfolio-snapshot.service.mock.ts | 4 +- .../portfolio-snapshot.service.ts | 4 +- .../interfaces/interfaces.ts | 2 +- .../rule-settings-dialog.component.ts | 4 +- .../src/app/components/rule/rule.component.ts | 4 +- .../alert-dialog/alert-dialog.component.ts | 4 +- .../alert-dialog/interfaces/interfaces.ts | 2 +- .../confirmation-dialog.component.ts | 4 +- .../interfaces/interfaces.ts | 2 +- .../notification/interfaces/interfaces.ts | 6 +-- .../core/notification/notification.service.ts | 12 +++--- apps/client/src/app/services/admin.service.ts | 4 +- apps/client/src/app/services/data.service.ts | 4 +- .../responses/dividends-response.interface.ts | 4 +- .../historical-response.interface.ts | 4 +- .../responses/quotes-response.interface.ts | 4 +- .../assistant-list-item.component.ts | 8 ++-- .../src/lib/assistant/assistant.component.ts | 40 +++++++++---------- .../lib/assistant/interfaces/interfaces.ts | 26 ++++++------ 45 files changed, 181 insertions(+), 183 deletions(-) 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 ac5881c4d..1094858cb 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,7 @@ import { GetQuotesParams, GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; -import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +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 { @@ -114,7 +114,7 @@ export class GhostfolioService { try { const promises: Promise<{ - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }>[] = []; for (const dataProviderService of this.getDataProviderServices()) { @@ -156,7 +156,7 @@ export class GhostfolioService { try { const promises: Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }>[] = []; for (const dataProviderService of this.getDataProviderServices()) { 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 a5b2823d5..fc9e61d61 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 { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { Controller, @@ -25,7 +25,7 @@ export class ExchangeRateController { public async getExchangeRate( @Param('dateString') dateString: string, @Param('symbol') symbol: string - ): Promise { + ): Promise { const date = parseISO(dateString); const exchangeRate = await this.exchangeRateService.getExchangeRate({ diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 8a8606003..3218d01f4 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -9,7 +9,7 @@ import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { @@ -193,7 +193,7 @@ export abstract class PortfolioCalculator { } const currencies: { [symbol: string]: string } = {}; - const dataGatheringItems: IDataGatheringItem[] = []; + const dataGatheringItems: DataGatheringItem[] = []; let firstIndex = transactionPoints.length; let firstTransactionPoint: TransactionPoint = null; let totalInterestWithCurrencyEffect = new Big(0); diff --git a/apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts b/apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts index 5cf7c8811..ffb74ee9b 100644 --- a/apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/get-values-params.interface.ts @@ -1,8 +1,8 @@ -import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { DateQuery } from './date-query.interface'; export interface GetValuesParams { - dataGatheringItems: IDataGatheringItem[]; + dataGatheringItems: DataGatheringItem[]; dateQuery: DateQuery; } diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index 5d9a49a29..b374a914b 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -1,7 +1,7 @@ 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 { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { LookupResponse } from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -97,7 +97,7 @@ export class SymbolController { @Param('dataSource') dataSource: DataSource, @Param('dateString') dateString: string, @Param('symbol') symbol: string - ): Promise { + ): Promise { const date = parseISO(dateString); if (!isDate(date)) { diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 56befb9b6..9eac234c9 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -1,7 +1,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { - IDataGatheringItem, - IDataProviderHistoricalResponse + DataGatheringItem, + DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; @@ -27,7 +27,7 @@ export class SymbolService { dataGatheringItem, includeHistoricalData }: { - dataGatheringItem: IDataGatheringItem; + dataGatheringItem: DataGatheringItem; includeHistoricalData?: number; }): Promise { const quotes = await this.dataProviderService.getQuotes({ @@ -75,10 +75,10 @@ export class SymbolService { dataSource, date = new Date(), symbol - }: IDataGatheringItem): Promise { + }: DataGatheringItem): Promise { let historicalData: { [symbol: string]: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }; } = { [symbol]: {} 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 1e8f7eefa..1e631f8c8 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 @@ -8,8 +8,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; @@ -23,7 +23,7 @@ import { DataSource, SymbolProfile } from '@prisma/client'; import * as Alphavantage from 'alphavantage'; import { format, isAfter, isBefore, parse } from 'date-fns'; -import { IAlphaVantageHistoricalResponse } from './interfaces/interfaces'; +import { AlphaVantageHistoricalResponse } from './interfaces/interfaces'; @Injectable() export class AlphaVantageService implements DataProviderInterface { @@ -68,11 +68,11 @@ export class AlphaVantageService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { try { const historicalData: { - [symbol: string]: IAlphaVantageHistoricalResponse[]; + [symbol: string]: AlphaVantageHistoricalResponse[]; } = await this.alphaVantage.crypto.daily( symbol .substring(0, symbol.length - DEFAULT_CURRENCY.length) @@ -81,7 +81,7 @@ export class AlphaVantageService implements DataProviderInterface { ); const response: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = {}; response[symbol] = {}; @@ -115,7 +115,7 @@ export class AlphaVantageService implements DataProviderInterface { } public async getQuotes({}: GetQuotesParams): Promise<{ - [symbol: string]: IDataProviderResponse; + [symbol: string]: DataProviderResponse; }> { return {}; } diff --git a/apps/api/src/services/data-provider/alpha-vantage/interfaces/interfaces.ts b/apps/api/src/services/data-provider/alpha-vantage/interfaces/interfaces.ts index d954f3a75..897351df1 100644 --- a/apps/api/src/services/data-provider/alpha-vantage/interfaces/interfaces.ts +++ b/apps/api/src/services/data-provider/alpha-vantage/interfaces/interfaces.ts @@ -1 +1 @@ -export interface IAlphaVantageHistoricalResponse {} +export interface AlphaVantageHistoricalResponse {} 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 561d7d6db..e06cb6ab3 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -8,8 +8,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; @@ -109,7 +109,7 @@ export class CoinGeckoService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { try { const { error, prices, status } = await fetch( @@ -133,7 +133,7 @@ export class CoinGeckoService implements DataProviderInterface { } const result: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = { [symbol]: {} }; @@ -166,8 +166,8 @@ export class CoinGeckoService implements DataProviderInterface { public async getQuotes({ requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - const response: { [symbol: string]: IDataProviderResponse } = {}; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { + const response: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return response; 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 6d6054287..53ef5c5e4 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -2,8 +2,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + 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'; @@ -215,10 +215,10 @@ export class DataProviderService implements OnModuleInit { from: Date, to: Date ): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { let response: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = {}; if (isEmpty(aItems) || !isValid(from) || !isValid(to)) { @@ -284,7 +284,7 @@ export class DataProviderService implements OnModuleInit { from: Date; to: Date; }): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { for (const { currency, rootCurrency } of DERIVED_CURRENCIES) { if ( @@ -317,11 +317,11 @@ export class DataProviderService implements OnModuleInit { ); const result: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = {}; const promises: Promise<{ - data: { [date: string]: IDataProviderHistoricalResponse }; + data: { [date: string]: DataProviderHistoricalResponse }; symbol: string; }>[] = []; for (const { dataSource, symbol } of assetProfileIdentifiers) { @@ -329,7 +329,7 @@ export class DataProviderService implements OnModuleInit { if (dataProvider.canHandle(symbol)) { if (symbol === `${DEFAULT_CURRENCY}USX`) { const data: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; for (const date of eachDayOfInterval({ end: to, start: from })) { @@ -399,10 +399,10 @@ export class DataProviderService implements OnModuleInit { useCache?: boolean; user?: UserWithSettings; }): Promise<{ - [symbol: string]: IDataProviderResponse; + [symbol: string]: DataProviderResponse; }> { const response: { - [symbol: string]: IDataProviderResponse; + [symbol: string]: DataProviderResponse; } = {}; const startTimeTotal = performance.now(); @@ -716,7 +716,7 @@ export class DataProviderService implements OnModuleInit { }: { allData: { data: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }; symbol: string; }[]; @@ -728,7 +728,7 @@ export class DataProviderService implements OnModuleInit { })?.data; const data: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; for (const date in rootData) { 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 c18ec193f..b837b2e6f 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 @@ -8,8 +8,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { @@ -89,7 +89,7 @@ export class EodHistoricalDataService implements DataProviderInterface { symbol, to }: GetDividendsParams): Promise<{ - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }> { symbol = this.convertToEodSymbol(symbol); @@ -99,7 +99,7 @@ export class EodHistoricalDataService implements DataProviderInterface { try { const response: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; const historicalResult = await fetch( @@ -141,7 +141,7 @@ export class EodHistoricalDataService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { symbol = this.convertToEodSymbol(symbol); @@ -198,8 +198,8 @@ export class EodHistoricalDataService implements DataProviderInterface { public async getQuotes({ requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - const response: { [symbol: string]: IDataProviderResponse } = {}; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { + const response: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return response; 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 689f59fec..0caad99ca 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 @@ -9,8 +9,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { @@ -245,7 +245,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { try { const response: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; const dividends = await fetch( @@ -289,11 +289,11 @@ export class FinancialModelingPrepService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { const MAX_YEARS_PER_REQUEST = 5; const result: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = { [symbol]: {} }; @@ -353,8 +353,8 @@ export class FinancialModelingPrepService implements DataProviderInterface { public async getQuotes({ requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - const response: { [symbol: string]: IDataProviderResponse } = {}; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { + const response: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return response; 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 ca8d72827..9928af8eb 100644 --- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -9,8 +9,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { @@ -111,10 +111,10 @@ export class GhostfolioService implements DataProviderInterface { symbol, to }: GetDividendsParams): Promise<{ - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }> { let dividends: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; try { @@ -164,7 +164,7 @@ export class GhostfolioService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { try { const response = await fetch( @@ -228,9 +228,9 @@ export class GhostfolioService implements DataProviderInterface { requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: GetQuotesParams): Promise<{ - [symbol: string]: IDataProviderResponse; + [symbol: string]: DataProviderResponse; }> { - let quotes: { [symbol: string]: IDataProviderResponse } = {}; + let quotes: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return quotes; 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 111f2d004..fc188c345 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 @@ -8,8 +8,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + 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'; @@ -60,7 +60,7 @@ export class GoogleSheetsService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { try { const sheet = await this.getSheet({ @@ -71,7 +71,7 @@ export class GoogleSheetsService implements DataProviderInterface { const rows = await sheet.getRows(); const historicalData: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; rows @@ -104,8 +104,8 @@ export class GoogleSheetsService implements DataProviderInterface { public async getQuotes({ symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - const response: { [symbol: string]: IDataProviderResponse } = {}; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { + const response: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return response; 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 475205a01..38eb62a2b 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,6 +1,6 @@ import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DataProviderInfo, @@ -26,7 +26,7 @@ export interface DataProviderInterface { symbol, to }: GetDividendsParams): Promise<{ - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }>; getHistorical({ @@ -36,7 +36,7 @@ export interface DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }>; // TODO: Return only one symbol getMaxNumberOfSymbolsPerRequest?(): number; @@ -46,7 +46,7 @@ export interface DataProviderInterface { getQuotes({ requestTimeout, symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }>; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }>; getTestSymbol(): string; 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 c411f678b..00c28d9d2 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -8,8 +8,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + 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'; @@ -77,7 +77,7 @@ export class ManualService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { try { const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles( @@ -88,7 +88,7 @@ export class ManualService implements DataProviderInterface { if (defaultMarketPrice) { const historical: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = { [symbol]: {} }; @@ -132,8 +132,8 @@ export class ManualService implements DataProviderInterface { public async getQuotes({ symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - const response: { [symbol: string]: IDataProviderResponse } = {}; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { + const response: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return response; diff --git a/apps/api/src/services/data-provider/rapid-api/interfaces/interfaces.ts b/apps/api/src/services/data-provider/rapid-api/interfaces/interfaces.ts index 995fdb9d3..f87a22639 100644 --- a/apps/api/src/services/data-provider/rapid-api/interfaces/interfaces.ts +++ b/apps/api/src/services/data-provider/rapid-api/interfaces/interfaces.ts @@ -1 +1 @@ -export interface IRapidApiResponse {} +export interface RapidApiResponse {} 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 824f44328..4d22e0feb 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 @@ -8,8 +8,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { ghostfolioFearAndGreedIndexSymbol, @@ -59,7 +59,7 @@ export class RapidApiService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { try { if ( @@ -96,7 +96,7 @@ export class RapidApiService implements DataProviderInterface { public async getQuotes({ symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { if (symbols.length <= 0) { return {}; } 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 390449d78..b36b0f215 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 @@ -10,8 +10,8 @@ import { GetSearchParams } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { - IDataProviderHistoricalResponse, - IDataProviderResponse + DataProviderHistoricalResponse, + DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; @@ -96,7 +96,7 @@ export class YahooFinanceService implements DataProviderInterface { ) ); const response: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; } = {}; for (const historicalItem of historicalResult) { @@ -124,7 +124,7 @@ export class YahooFinanceService implements DataProviderInterface { symbol, to }: GetHistoricalParams): Promise<{ - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; }> { if (isSameDay(from, to)) { to = addDays(to, 1); @@ -145,7 +145,7 @@ export class YahooFinanceService implements DataProviderInterface { ); const response: { - [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + [symbol: string]: { [date: string]: DataProviderHistoricalResponse }; } = {}; response[symbol] = {}; @@ -183,8 +183,8 @@ export class YahooFinanceService implements DataProviderInterface { public async getQuotes({ symbols - }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - const response: { [symbol: string]: IDataProviderResponse } = {}; + }: GetQuotesParams): Promise<{ [symbol: string]: DataProviderResponse }> { + const response: { [symbol: string]: DataProviderResponse } = {}; if (symbols.length <= 0) { return response; 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 433547c94..47c67c3de 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 @@ -1,6 +1,6 @@ import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; -import { IDataGatheringItem } 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 { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; @@ -29,7 +29,7 @@ import ms from 'ms'; @Injectable() export class ExchangeRateDataService { private currencies: string[] = []; - private currencyPairs: IDataGatheringItem[] = []; + private currencyPairs: DataGatheringItem[] = []; private exchangeRates: { [currencyPair: string]: number } = {}; public constructor( diff --git a/apps/api/src/services/interfaces/interfaces.ts b/apps/api/src/services/interfaces/interfaces.ts index 0eaa149a3..7469754b5 100644 --- a/apps/api/src/services/interfaces/interfaces.ts +++ b/apps/api/src/services/interfaces/interfaces.ts @@ -6,11 +6,11 @@ import { MarketState } from '@ghostfolio/common/types'; import { DataSource } from '@prisma/client'; -export interface IDataProviderHistoricalResponse { +export interface DataProviderHistoricalResponse { marketPrice: number; } -export interface IDataProviderResponse { +export interface DataProviderResponse { currency: string; dataProviderInfo?: DataProviderInfo; dataSource: DataSource; @@ -18,6 +18,6 @@ export interface IDataProviderResponse { marketState: MarketState; } -export interface IDataGatheringItem extends AssetProfileIdentifier { +export interface DataGatheringItem extends AssetProfileIdentifier { date?: Date; } 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 58b9b09ec..38ad61663 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -1,6 +1,6 @@ import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface'; -import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { resetHours } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; @@ -30,7 +30,7 @@ export class MarketDataService { dataSource, date = new Date(), symbol - }: IDataGatheringItem): Promise { + }: DataGatheringItem): Promise { return await this.prismaService.marketData.findFirst({ where: { dataSource, diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts index 9cf6f63e6..1a172f3ea 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts @@ -1,6 +1,6 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { AssetProfileDelistedError } from '@ghostfolio/api/services/data-provider/errors/asset-profile-delisted.error'; -import { IDataGatheringItem } 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 { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { @@ -99,7 +99,7 @@ export class DataGatheringProcessor { ), name: GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME }) - public async gatherHistoricalMarketData(job: Job) { + public async gatherHistoricalMarketData(job: Job) { const { dataSource, date, symbol } = job.data; try { 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 dd93e3e47..2d3ec45ad 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 @@ -1,7 +1,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { IDataGatheringItem } 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 { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; @@ -94,7 +94,7 @@ export class DataGatheringService { }); } - public async gatherSymbol({ dataSource, date, symbol }: IDataGatheringItem) { + public async gatherSymbol({ dataSource, date, symbol }: DataGatheringItem) { await this.marketDataService.deleteMany({ dataSource, symbol }); const dataGatheringItems = (await this.getSymbolsMax()) @@ -276,7 +276,7 @@ export class DataGatheringService { dataGatheringItems, priority }: { - dataGatheringItems: IDataGatheringItem[]; + dataGatheringItems: DataGatheringItem[]; priority: number; }) { await this.addJobsToQueue( @@ -348,7 +348,7 @@ export class DataGatheringService { }); } - private async getCurrencies7D(): Promise { + private async getCurrencies7D(): Promise { const assetProfileIdentifiersWithCompleteMarketData = await this.getAssetProfileIdentifiersWithCompleteMarketData(); @@ -376,7 +376,7 @@ export class DataGatheringService { withUserSubscription = false }: { withUserSubscription?: boolean; - }): Promise { + }): Promise { const symbolProfiles = await this.symbolProfileService.getActiveSymbolProfilesByUserSubscription( { @@ -407,7 +407,7 @@ export class DataGatheringService { }); } - private async getSymbolsMax(): Promise { + private async getSymbolsMax(): Promise { const benchmarkAssetProfileIdMap: { [key: string]: boolean } = {}; ( (await this.propertyService.getByKey( diff --git a/apps/api/src/services/queues/portfolio-snapshot/interfaces/portfolio-snapshot-queue-job.interface.ts b/apps/api/src/services/queues/portfolio-snapshot/interfaces/portfolio-snapshot-queue-job.interface.ts index b9f315c5d..3486974f7 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/interfaces/portfolio-snapshot-queue-job.interface.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/interfaces/portfolio-snapshot-queue-job.interface.ts @@ -1,7 +1,7 @@ import { Filter } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; -export interface IPortfolioSnapshotQueueJob { +export interface PortfolioSnapshotQueueJob { calculationType: PerformanceCalculationType; filters: Filter[]; userCurrency: string; 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 6a2a3114e..75a3a8631 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 @@ -16,7 +16,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { Job } from 'bull'; import { addMilliseconds } from 'date-fns'; -import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface'; +import { PortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface'; @Injectable() @Processor(PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE) @@ -37,9 +37,7 @@ export class PortfolioSnapshotProcessor { ), name: PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME }) - public async calculatePortfolioSnapshot( - job: Job - ) { + public async calculatePortfolioSnapshot(job: Job) { try { const startTime = performance.now(); diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts index 59fdc5855..898718106 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts @@ -1,13 +1,13 @@ import { Job, JobOptions } from 'bull'; import { setTimeout } from 'timers/promises'; -import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface'; +import { PortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface'; export const PortfolioSnapshotServiceMock = { addJobToQueue({ opts }: { - data: IPortfolioSnapshotQueueJob; + data: PortfolioSnapshotQueueJob; name: string; opts?: JobOptions; }): Promise> { diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.ts index 9dba9275e..d7449a9cc 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.ts @@ -4,7 +4,7 @@ import { InjectQueue } from '@nestjs/bull'; import { Injectable } from '@nestjs/common'; import { JobOptions, Queue } from 'bull'; -import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface'; +import { PortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface'; @Injectable() export class PortfolioSnapshotService { @@ -18,7 +18,7 @@ export class PortfolioSnapshotService { name, opts }: { - data: IPortfolioSnapshotQueueJob; + data: PortfolioSnapshotQueueJob; name: string; opts?: JobOptions; }) { diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts index 51c2b8951..90a0039cf 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts @@ -3,7 +3,7 @@ import { XRayRulesSettings } from '@ghostfolio/common/interfaces'; -export interface IRuleSettingsDialogParams { +export interface RuleSettingsDialogParams { categoryName: string; locale: string; rule: PortfolioReportRule; diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts index f8ce13e0d..65300c6d8 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts @@ -12,7 +12,7 @@ import { } from '@angular/material/dialog'; import { MatSliderModule } from '@angular/material/slider'; -import { IRuleSettingsDialogParams } from './interfaces/interfaces'; +import { RuleSettingsDialogParams } from './interfaces/interfaces'; @Component({ imports: [ @@ -31,7 +31,7 @@ export class GfRuleSettingsDialogComponent { public settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; public constructor( - @Inject(MAT_DIALOG_DATA) public data: IRuleSettingsDialogParams, + @Inject(MAT_DIALOG_DATA) public data: RuleSettingsDialogParams, public dialogRef: MatDialogRef ) {} } diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index c38de8bbb..ba77ce162 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -31,7 +31,7 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, takeUntil } from 'rxjs'; -import { IRuleSettingsDialogParams } from './rule-settings-dialog/interfaces/interfaces'; +import { RuleSettingsDialogParams } from './rule-settings-dialog/interfaces/interfaces'; import { GfRuleSettingsDialogComponent } from './rule-settings-dialog/rule-settings-dialog.component'; @Component({ @@ -83,7 +83,7 @@ export class GfRuleComponent implements OnInit { rule, categoryName: this.categoryName, settings: this.settings - } as IRuleSettingsDialogParams, + } as RuleSettingsDialogParams, width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); diff --git a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts b/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts index 98b6043eb..33d26c493 100644 --- a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts +++ b/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; -import { IAlertDialogParams } from './interfaces/interfaces'; +import { AlertDialogParams } from './interfaces/interfaces'; @Component({ imports: [MatButtonModule, MatDialogModule], @@ -17,7 +17,7 @@ export class GfAlertDialogComponent { public constructor(public dialogRef: MatDialogRef) {} - public initialize(aParams: IAlertDialogParams) { + public initialize(aParams: AlertDialogParams) { this.discardLabel = aParams.discardLabel; this.message = aParams.message; this.title = aParams.title; diff --git a/apps/client/src/app/core/notification/alert-dialog/interfaces/interfaces.ts b/apps/client/src/app/core/notification/alert-dialog/interfaces/interfaces.ts index 7cff077a7..835056ba7 100644 --- a/apps/client/src/app/core/notification/alert-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/core/notification/alert-dialog/interfaces/interfaces.ts @@ -1,4 +1,4 @@ -export interface IAlertDialogParams { +export interface AlertDialogParams { confirmLabel?: string; discardLabel?: string; message?: string; 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 88e5113d7..49c6dc5a3 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 @@ -3,7 +3,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { ConfirmationDialogType } from './confirmation-dialog.type'; -import { IConfirmDialogParams } from './interfaces/interfaces'; +import { ConfirmDialogParams } from './interfaces/interfaces'; @Component({ imports: [MatButtonModule, MatDialogModule], @@ -29,7 +29,7 @@ export class GfConfirmationDialogComponent { } } - public initialize(aParams: IConfirmDialogParams) { + public initialize(aParams: ConfirmDialogParams) { this.confirmLabel = aParams.confirmLabel; this.confirmType = aParams.confirmType; this.discardLabel = aParams.discardLabel; 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 834988ceb..8788e54fe 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,6 +1,6 @@ import { ConfirmationDialogType } from '../confirmation-dialog.type'; -export interface IConfirmDialogParams { +export interface ConfirmDialogParams { confirmLabel?: string; confirmType: ConfirmationDialogType; discardLabel?: string; diff --git a/apps/client/src/app/core/notification/interfaces/interfaces.ts b/apps/client/src/app/core/notification/interfaces/interfaces.ts index f3546d457..c58c7fa28 100644 --- a/apps/client/src/app/core/notification/interfaces/interfaces.ts +++ b/apps/client/src/app/core/notification/interfaces/interfaces.ts @@ -1,13 +1,13 @@ import { ConfirmationDialogType } from '../confirmation-dialog/confirmation-dialog.type'; -export interface IAlertParams { +export interface AlertParams { discardFn?: () => void; discardLabel?: string; message?: string; title: string; } -export interface IConfirmParams { +export interface ConfirmParams { confirmFn: () => void; confirmLabel?: string; confirmType?: ConfirmationDialogType; @@ -18,7 +18,7 @@ export interface IConfirmParams { title: string; } -export interface IPromptParams { +export interface PromptParams { confirmFn: (value: string) => void; confirmLabel?: string; defaultValue?: string; diff --git a/apps/client/src/app/core/notification/notification.service.ts b/apps/client/src/app/core/notification/notification.service.ts index 1b58db64a..9c31aa7bd 100644 --- a/apps/client/src/app/core/notification/notification.service.ts +++ b/apps/client/src/app/core/notification/notification.service.ts @@ -8,9 +8,9 @@ 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 { - IAlertParams, - IConfirmParams, - IPromptParams + AlertParams, + ConfirmParams, + PromptParams } from './interfaces/interfaces'; import { GfPromptDialogComponent } from './prompt-dialog/prompt-dialog.component'; @@ -21,7 +21,7 @@ export class NotificationService { public constructor(private matDialog: MatDialog) {} - public alert(aParams: IAlertParams) { + public alert(aParams: AlertParams) { if (!aParams.discardLabel) { aParams.discardLabel = translate('CLOSE'); } @@ -45,7 +45,7 @@ export class NotificationService { }); } - public confirm(aParams: IConfirmParams) { + public confirm(aParams: ConfirmParams) { if (!aParams.confirmLabel) { aParams.confirmLabel = translate('YES'); } @@ -78,7 +78,7 @@ export class NotificationService { }); } - public prompt(aParams: IPromptParams) { + public prompt(aParams: PromptParams) { if (!aParams.confirmLabel) { aParams.confirmLabel = translate('OK'); } diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 6a90bc4df..a04ad8d56 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -1,7 +1,7 @@ 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 { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { HEADER_KEY_SKIP_INTERCEPTOR, HEADER_KEY_TOKEN @@ -208,7 +208,7 @@ export class AdminService { }) { const url = `/api/v1/symbol/${dataSource}/${symbol}/${dateString}`; - return this.http.get(url); + return this.http.get(url); } public patchAssetProfile( diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 3cb5a8c75..74d21bf41 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -19,7 +19,7 @@ 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 { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +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 { @@ -291,7 +291,7 @@ export class DataService { date: Date; symbol: string; }) { - return this.http.get( + return this.http.get( `/api/v1/exchange-rate/${symbol}/${format(date, DATE_FORMAT, { in: utc })}` ); } 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 f7cacf89a..15afc54c9 100644 --- a/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts @@ -1,7 +1,7 @@ -import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; export interface DividendsResponse { dividends: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }; } 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 12309a352..24383ab07 100644 --- a/libs/common/src/lib/interfaces/responses/historical-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/historical-response.interface.ts @@ -1,7 +1,7 @@ -import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; export interface HistoricalResponse { historicalData: { - [date: string]: IDataProviderHistoricalResponse; + [date: string]: DataProviderHistoricalResponse; }; } 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 79c9d3024..8b9b09cb8 100644 --- a/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts @@ -1,5 +1,5 @@ -import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; export interface QuotesResponse { - quotes: { [symbol: string]: IDataProviderResponse }; + quotes: { [symbol: string]: DataProviderResponse }; } 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 f75aaea01..f9034df71 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 @@ -18,8 +18,8 @@ import { Params, RouterModule } from '@angular/router'; import { SearchMode } from '../enums/search-mode'; import { - IAssetSearchResultItem, - ISearchResultItem + AssetSearchResultItem, + SearchResultItem } from '../interfaces/interfaces'; @Component({ @@ -37,7 +37,7 @@ export class GfAssistantListItemComponent return this.hasFocus; } - @Input() item: ISearchResultItem; + @Input() item: SearchResultItem; @Output() clicked = new EventEmitter(); @@ -86,7 +86,7 @@ export class GfAssistantListItemComponent this.changeDetectorRef.markForCheck(); } - public isAsset(item: ISearchResultItem): item is IAssetSearchResultItem { + public isAsset(item: SearchResultItem): item is AssetSearchResultItem { return ( (item.mode === SearchMode.ASSET_PROFILE || item.mode === SearchMode.HOLDING) && diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 3fc1cc232..8c04c306f 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -65,9 +65,9 @@ import { translate } from '../i18n'; import { GfAssistantListItemComponent } from './assistant-list-item/assistant-list-item.component'; import { SearchMode } from './enums/search-mode'; import { - IDateRangeOption, - ISearchResultItem, - ISearchResults + DateRangeOption, + SearchResultItem, + SearchResults } from './interfaces/interfaces'; @Component({ @@ -144,7 +144,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public accounts: Account[] = []; public assetClasses: Filter[] = []; public dateRangeFormControl = new FormControl(undefined); - public dateRangeOptions: IDateRangeOption[] = []; + public dateRangeOptions: DateRangeOption[] = []; public filterForm = this.formBuilder.group({ account: new FormControl(undefined), assetClass: new FormControl(undefined), @@ -161,7 +161,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public isOpen = false; public placeholder = $localize`Find account, holding or page...`; public searchFormControl = new FormControl(''); - public searchResults: ISearchResults = { + public searchResults: SearchResults = { accounts: [], assetProfiles: [], holdings: [], @@ -229,7 +229,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { assetProfiles: [], holdings: [], quickLinks: [] - } as ISearchResults; + } as SearchResults; if (!searchTerm) { return of(results).pipe( @@ -245,7 +245,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { } // Accounts - const accounts$: Observable> = + const accounts$: Observable> = this.searchAccounts(searchTerm).pipe( map((accounts) => ({ accounts: accounts.slice( @@ -255,7 +255,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { })), catchError((error) => { console.error('Error fetching accounts for assistant:', error); - return of({ accounts: [] as ISearchResultItem[] }); + return of({ accounts: [] as SearchResultItem[] }); }), tap(() => { this.isLoading.accounts = false; @@ -264,7 +264,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); // Asset profiles - const assetProfiles$: Observable> = this + const assetProfiles$: Observable> = this .hasPermissionToAccessAdminControl ? this.searchAssetProfiles(searchTerm).pipe( map((assetProfiles) => ({ @@ -278,14 +278,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { 'Error fetching asset profiles for assistant:', error ); - return of({ assetProfiles: [] as ISearchResultItem[] }); + return of({ assetProfiles: [] as SearchResultItem[] }); }), tap(() => { this.isLoading.assetProfiles = false; this.changeDetectorRef.markForCheck(); }) ) - : of({ assetProfiles: [] as ISearchResultItem[] }).pipe( + : of({ assetProfiles: [] as SearchResultItem[] }).pipe( tap(() => { this.isLoading.assetProfiles = false; this.changeDetectorRef.markForCheck(); @@ -293,7 +293,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); // Holdings - const holdings$: Observable> = + const holdings$: Observable> = this.searchHoldings(searchTerm).pipe( map((holdings) => ({ holdings: holdings.slice( @@ -303,7 +303,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { })), catchError((error) => { console.error('Error fetching holdings for assistant:', error); - return of({ holdings: [] as ISearchResultItem[] }); + return of({ holdings: [] as SearchResultItem[] }); }), tap(() => { this.isLoading.holdings = false; @@ -312,7 +312,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); // Quick links - const quickLinks$: Observable> = of( + const quickLinks$: Observable> = of( this.searchQuickLinks(searchTerm) ).pipe( map((quickLinks) => ({ @@ -330,7 +330,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { // Merge all results return merge(accounts$, assetProfiles$, holdings$, quickLinks$).pipe( scan( - (acc: ISearchResults, curr: Partial) => ({ + (acc: SearchResults, curr: Partial) => ({ ...acc, ...curr }), @@ -339,7 +339,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { assetProfiles: [], holdings: [], quickLinks: [] - } as ISearchResults + } as SearchResults ) ); }), @@ -658,7 +658,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }, this.PRESELECTION_DELAY); } - private searchAccounts(aSearchTerm: string): Observable { + private searchAccounts(aSearchTerm: string): Observable { return this.dataService .fetchAccounts({ filters: [ @@ -688,7 +688,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { private searchAssetProfiles( aSearchTerm: string - ): Observable { + ): Observable { return this.adminService .fetchAdminMarketData({ filters: [ @@ -721,7 +721,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); } - private searchHoldings(aSearchTerm: string): Observable { + private searchHoldings(aSearchTerm: string): Observable { return this.dataService .fetchPortfolioHoldings({ filters: [ @@ -753,7 +753,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); } - private searchQuickLinks(aSearchTerm: string): ISearchResultItem[] { + private searchQuickLinks(aSearchTerm: string): SearchResultItem[] { const searchTerm = aSearchTerm.toLowerCase(); const allRoutes = Object.values(internalRoutes) diff --git a/libs/ui/src/lib/assistant/interfaces/interfaces.ts b/libs/ui/src/lib/assistant/interfaces/interfaces.ts index 247641094..e018e0eb6 100644 --- a/libs/ui/src/lib/assistant/interfaces/interfaces.ts +++ b/libs/ui/src/lib/assistant/interfaces/interfaces.ts @@ -3,38 +3,38 @@ import { AccountWithValue, DateRange } from '@ghostfolio/common/types'; import { SearchMode } from '../enums/search-mode'; -export interface IAccountSearchResultItem +export interface AccountSearchResultItem extends Pick { mode: SearchMode.ACCOUNT; routerLink: string[]; } -export interface IAssetSearchResultItem extends AssetProfileIdentifier { +export interface AssetSearchResultItem extends AssetProfileIdentifier { assetSubClassString: string; currency: string; mode: SearchMode.ASSET_PROFILE | SearchMode.HOLDING; name: string; } -export interface IDateRangeOption { +export interface DateRangeOption { label: string; value: DateRange; } -export interface IQuickLinkSearchResultItem { +export interface QuickLinkSearchResultItem { mode: SearchMode.QUICK_LINK; name: string; routerLink: string[]; } -export type ISearchResultItem = - | IAccountSearchResultItem - | IAssetSearchResultItem - | IQuickLinkSearchResultItem; +export type SearchResultItem = + | AccountSearchResultItem + | AssetSearchResultItem + | QuickLinkSearchResultItem; -export interface ISearchResults { - accounts: ISearchResultItem[]; - assetProfiles: ISearchResultItem[]; - holdings: ISearchResultItem[]; - quickLinks: ISearchResultItem[]; +export interface SearchResults { + accounts: SearchResultItem[]; + assetProfiles: SearchResultItem[]; + holdings: SearchResultItem[]; + quickLinks: SearchResultItem[]; } From 3b4705405bd166de92c67597fa0b0d3c55cb6e3b Mon Sep 17 00:00:00 2001 From: Vansh <140736931+Vansh-Parate@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:09:09 +0530 Subject: [PATCH 005/146] Task/improve typings of getAsset() functionality (#5804) * Improve typings of getAsset() functionality --- apps/api/src/app/asset/asset.controller.ts | 4 ++-- apps/client/src/app/services/data.service.ts | 3 ++- libs/common/src/lib/interfaces/index.ts | 2 ++ .../src/lib/interfaces/responses/asset-response.interface.ts | 3 +++ 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 libs/common/src/lib/interfaces/responses/asset-response.interface.ts diff --git a/apps/api/src/app/asset/asset.controller.ts b/apps/api/src/app/asset/asset.controller.ts index 828320f82..3b2031084 100644 --- a/apps/api/src/app/asset/asset.controller.ts +++ b/apps/api/src/app/asset/asset.controller.ts @@ -1,7 +1,7 @@ import { AdminService } from '@ghostfolio/api/app/admin/admin.service'; 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 type { AdminMarketDataDetails } from '@ghostfolio/common/interfaces'; +import type { AssetResponse } from '@ghostfolio/common/interfaces'; import { Controller, Get, Param, UseInterceptors } from '@nestjs/common'; import { DataSource } from '@prisma/client'; @@ -17,7 +17,7 @@ export class AssetController { public async getAsset( @Param('dataSource') dataSource: DataSource, @Param('symbol') symbol: string - ): Promise { + ): Promise { const { assetProfile, marketData } = await this.adminService.getMarketDataBySymbol({ dataSource, symbol }); diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 74d21bf41..66d84c09d 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -30,6 +30,7 @@ import { AiPromptResponse, ApiKeyResponse, AssetProfileIdentifier, + AssetResponse, BenchmarkMarketDataDetailsResponse, BenchmarkResponse, DataProviderHealthResponse, @@ -345,7 +346,7 @@ export class DataService { public fetchAsset({ dataSource, symbol - }: AssetProfileIdentifier): Observable { + }: AssetProfileIdentifier): Observable { return this.http.get(`/api/v1/asset/${dataSource}/${symbol}`).pipe( map((data) => { for (const item of data.marketData) { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index b69891e31..854c53df0 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -40,6 +40,7 @@ import type { AccountBalancesResponse } from './responses/account-balances-respo import type { AccountsResponse } from './responses/accounts-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface'; +import type { AssetResponse } from './responses/asset-response.interface'; import type { BenchmarkMarketDataDetailsResponse } from './responses/benchmark-market-data-details-response.interface'; import type { BenchmarkResponse } from './responses/benchmark-response.interface'; import type { DataEnhancerHealthResponse } from './responses/data-enhancer-health-response.interface'; @@ -91,6 +92,7 @@ export { ApiKeyResponse, AssetClassSelectorOption, AssetProfileIdentifier, + AssetResponse, Benchmark, BenchmarkMarketDataDetailsResponse, BenchmarkProperty, diff --git a/libs/common/src/lib/interfaces/responses/asset-response.interface.ts b/libs/common/src/lib/interfaces/responses/asset-response.interface.ts new file mode 100644 index 000000000..452ec0d3d --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/asset-response.interface.ts @@ -0,0 +1,3 @@ +import type { AdminMarketDataDetails } from '../admin-market-data-details.interface'; + +export interface AssetResponse extends AdminMarketDataDetails {} From 32152806367b0cfa9cbe77e394507200cff7533a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Mon, 20 Oct 2025 20:17:05 +0200 Subject: [PATCH 006/146] Task/extract portfolio filter sub form of assistant to reusable component (#5618) * Extract portfolio filter sub form of assistant to reusable component * Update changelog --- CHANGELOG.md | 1 + .../src/lib/interfaces/user.interface.ts | 9 +- .../src/lib/assistant/assistant.component.ts | 123 ++++-------- libs/ui/src/lib/assistant/assistant.html | 170 ++++++----------- .../ui/src/lib/portfolio-filter-form/index.ts | 2 + .../portfolio-filter-form/interfaces/index.ts | 1 + .../portfolio-filter-form-value.interface.ts | 8 + .../portfolio-filter-form.component.html | 75 ++++++++ .../portfolio-filter-form.component.scss | 3 + ...portfolio-filter-form.component.stories.ts | 79 ++++++++ .../portfolio-filter-form.component.ts | 177 ++++++++++++++++++ 11 files changed, 449 insertions(+), 199 deletions(-) create mode 100644 libs/ui/src/lib/portfolio-filter-form/index.ts create mode 100644 libs/ui/src/lib/portfolio-filter-form/interfaces/index.ts create mode 100644 libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts create mode 100644 libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html create mode 100644 libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.scss create mode 100644 libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts create mode 100644 libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e783ae690..08201a175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Extracted the portfolio filter form of the assistant to a reusable component - Formatted the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) - Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) - Improved the language localization for German (`de`) diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index a48317fad..2e0906895 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -1,6 +1,9 @@ -import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; +import { + AccountWithPlatform, + SubscriptionType +} from '@ghostfolio/common/types'; -import { Access, Account, Tag } from '@prisma/client'; +import { Access, Tag } from '@prisma/client'; import { SubscriptionOffer } from './subscription-offer.interface'; import { SystemMessage } from './system-message.interface'; @@ -9,7 +12,7 @@ import { UserSettings } from './user-settings.interface'; // TODO: Compare with UserWithSettings export interface User { access: Pick[]; - accounts: Account[]; + accounts: AccountWithPlatform[]; activitiesCount: number; dateOfFirstActivity: Date; id: string; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 8c04c306f..eaf96f496 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -1,11 +1,10 @@ -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 { 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 { DateRange } from '@ghostfolio/common/types'; +import { AccountWithPlatform, DateRange } from '@ghostfolio/common/types'; import { FocusKeyManager } from '@angular/cdk/a11y'; import { @@ -25,19 +24,14 @@ import { ViewChild, ViewChildren } from '@angular/core'; -import { - FormBuilder, - FormControl, - FormsModule, - ReactiveFormsModule -} from '@angular/forms'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatMenuTrigger } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; -import { Account, AssetClass, DataSource } from '@prisma/client'; +import { AssetClass, DataSource } from '@prisma/client'; import { differenceInYears } from 'date-fns'; import Fuse from 'fuse.js'; import { addIcons } from 'ionicons'; @@ -60,8 +54,11 @@ import { tap } from 'rxjs/operators'; -import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component'; import { translate } from '../i18n'; +import { + GfPortfolioFilterFormComponent, + PortfolioFilterFormValue +} from '../portfolio-filter-form'; import { GfAssistantListItemComponent } from './assistant-list-item/assistant-list-item.component'; import { SearchMode } from './enums/search-mode'; import { @@ -75,8 +72,7 @@ import { imports: [ FormsModule, GfAssistantListItemComponent, - GfEntityLogoComponent, - GfSymbolPipe, + GfPortfolioFilterFormComponent, IonIcon, MatButtonModule, MatFormFieldModule, @@ -141,16 +137,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; - public accounts: Account[] = []; + public accounts: AccountWithPlatform[] = []; public assetClasses: Filter[] = []; public dateRangeFormControl = new FormControl(undefined); public dateRangeOptions: DateRangeOption[] = []; - public filterForm = this.formBuilder.group({ - account: new FormControl(undefined), - assetClass: new FormControl(undefined), - holding: new FormControl(undefined), - tag: new FormControl(undefined) - }); public holdings: PortfolioPosition[] = []; public isLoading = { accounts: false, @@ -160,6 +150,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }; public isOpen = false; public placeholder = $localize`Find account, holding or page...`; + public portfolioFilterFormControl = new FormControl( + { + account: null, + assetClass: null, + holding: null, + tag: null + } + ); public searchFormControl = new FormControl(''); public searchResults: SearchResults = { accounts: [], @@ -186,8 +184,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private formBuilder: FormBuilder + private dataService: DataService ) { addIcons({ closeCircleOutline, closeOutline, searchOutline }); } @@ -244,7 +241,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); } - // Accounts const accounts$: Observable> = this.searchAccounts(searchTerm).pipe( map((accounts) => ({ @@ -263,7 +259,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }) ); - // Asset profiles const assetProfiles$: Observable> = this .hasPermissionToAccessAdminControl ? this.searchAssetProfiles(searchTerm).pipe( @@ -292,7 +287,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }) ); - // Holdings const holdings$: Observable> = this.searchHoldings(searchTerm).pipe( map((holdings) => ({ @@ -311,7 +305,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }) ); - // Quick links const quickLinks$: Observable> = of( this.searchQuickLinks(searchTerm) ).pipe( @@ -327,7 +320,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }) ); - // Merge all results return merge(accounts$, assetProfiles$, holdings$, quickLinks$).pipe( scan( (acc: SearchResults, curr: Partial) => ({ @@ -362,22 +354,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { quickLinks: [] }; this.changeDetectorRef.markForCheck(); - }, - complete: () => { - this.isLoading = { - accounts: false, - assetProfiles: false, - holdings: false, - quickLinks: false - }; - this.changeDetectorRef.markForCheck(); } }); } public ngOnChanges() { - this.accounts = this.user?.accounts ?? []; - this.dateRangeOptions = [ { label: $localize`Today`, @@ -445,7 +426,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.dateRangeFormControl.setValue(this.user?.settings?.dateRange ?? null); - this.filterForm.disable({ emitEvent: false }); + if (this.hasPermissionToChangeFilters) { + this.portfolioFilterFormControl.enable({ emitEvent: false }); + } else { + this.portfolioFilterFormControl.disable({ emitEvent: false }); + } this.tags = this.user?.tags @@ -459,29 +444,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { type: 'TAG' }; }) ?? []; - - if (this.tags.length === 0) { - this.filterForm.get('tag').disable({ emitEvent: false }); - } - } - - public hasFilter(aFormValue: { [key: string]: string }) { - return Object.values(aFormValue).some((value) => { - return !!value; - }); - } - - public holdingComparisonFunction( - option: PortfolioPosition, - value: PortfolioPosition - ): boolean { - if (value === null) { - return false; - } - - return ( - getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value) - ); } public initialize() { @@ -527,36 +489,35 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { .sort((a, b) => { return a.name?.localeCompare(b.name); }); - this.setFilterFormValues(); - if (this.hasPermissionToChangeFilters) { - this.filterForm.enable({ emitEvent: false }); - } + this.setPortfolioFilterFormValues(); this.changeDetectorRef.markForCheck(); }); } public onApplyFilters() { + const filterValue = this.portfolioFilterFormControl.value; + this.filtersChanged.emit([ { - id: this.filterForm.get('account').value, + id: filterValue?.account, type: 'ACCOUNT' }, { - id: this.filterForm.get('assetClass').value, + id: filterValue?.assetClass, type: 'ASSET_CLASS' }, { - id: this.filterForm.get('holding').value?.dataSource, + id: filterValue?.holding?.dataSource, type: 'DATA_SOURCE' }, { - id: this.filterForm.get('holding').value?.symbol, + id: filterValue?.holding?.symbol, type: 'SYMBOL' }, { - id: this.filterForm.get('tag').value, + id: filterValue?.tag, type: 'TAG' } ]); @@ -569,12 +530,15 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { } public onCloseAssistant() { + this.portfolioFilterFormControl.reset(); this.setIsOpen(false); this.closed.emit(); } public onResetFilters() { + this.portfolioFilterFormControl.reset(); + this.filtersChanged.emit( this.filterTypes.map((type) => { return { @@ -786,7 +750,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }); } - private setFilterFormValues() { + private setPortfolioFilterFormValues() { const dataSource = this.user?.settings?.[ 'filters.dataSource' ] as DataSource; @@ -800,16 +764,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); }); - this.filterForm.setValue( - { - account: this.user?.settings?.['filters.accounts']?.[0] ?? null, - assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null, - holding: selectedHolding ?? null, - tag: this.user?.settings?.['filters.tags']?.[0] ?? null - }, - { - emitEvent: false - } - ); + this.portfolioFilterFormControl.setValue({ + account: this.user?.settings?.['filters.accounts']?.[0] ?? null, + assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null, + holding: selectedHolding ?? null, + tag: this.user?.settings?.['filters.tags']?.[0] ?? null + }); } } diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index 5954ce369..e0a8f2fc9 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -164,119 +164,61 @@
} -
- @if (!searchFormControl.value) { -
- - Date Range - - @for (range of dateRangeOptions; track range) { - {{ range.label }} - } - - -
-
-
- - Account - - - @for (account of accounts; track account.id) { - -
- @if (account.platform?.url) { - - } - {{ account.name }} -
-
- } -
-
-
-
- - Holding - - {{ - filterForm.get('holding')?.value?.name - }} - - @for (holding of holdings; track holding.name) { - -
- {{ holding.name }} -
- {{ holding.symbol | gfSymbol }} · - {{ holding.currency }} -
-
- } -
-
-
-
- - Tag - - - @for (tag of tags; track tag.id) { - {{ tag.label }} - } - - -
-
- - Asset Class - - - @for (assetClass of assetClasses; track assetClass.id) { - {{ - assetClass.label - }} - } - - -
-
- - - -
+ @if (!searchFormControl.value) { +
+ + Date Range + + @for ( + dateRangeOption of dateRangeOptions; + track dateRangeOption.value + ) { + {{ + dateRangeOption.label + }} + } + + +
+
+ +
+ + +
- } - +
+ }
diff --git a/libs/ui/src/lib/portfolio-filter-form/index.ts b/libs/ui/src/lib/portfolio-filter-form/index.ts new file mode 100644 index 000000000..51d22c034 --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/index.ts @@ -0,0 +1,2 @@ +export * from './interfaces'; +export * from './portfolio-filter-form.component'; diff --git a/libs/ui/src/lib/portfolio-filter-form/interfaces/index.ts b/libs/ui/src/lib/portfolio-filter-form/interfaces/index.ts new file mode 100644 index 000000000..62feaa56a --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/interfaces/index.ts @@ -0,0 +1 @@ +export * from './portfolio-filter-form-value.interface'; diff --git a/libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts b/libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts new file mode 100644 index 000000000..21ff0ae3b --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts @@ -0,0 +1,8 @@ +import { PortfolioPosition } from '@ghostfolio/common/interfaces'; + +export interface PortfolioFilterFormValue { + account: string; + assetClass: string; + holding: PortfolioPosition; + tag: string; +} diff --git a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html new file mode 100644 index 000000000..e017d33d6 --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html @@ -0,0 +1,75 @@ +
+
+ + Account + + + @for (account of accounts; track account.id) { + +
+ @if (account.platform?.url) { + + } + {{ account.name }} +
+
+ } +
+
+
+
+ + Holding + + {{ + filterForm.get('holding')?.value?.name + }} + + @for (holding of holdings; track holding.name) { + +
+ {{ holding.name }} +
+ {{ holding.symbol | gfSymbol }} · {{ holding.currency }} +
+
+ } +
+
+
+
+ + Tag + + + @for (tag of tags; track tag.id) { + {{ tag.label }} + } + + +
+
+ + Asset Class + + + @for (assetClass of assetClasses; track assetClass.id) { + {{ + assetClass.label + }} + } + + +
+
diff --git a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.scss b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.scss new file mode 100644 index 000000000..5d4e87f30 --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts new file mode 100644 index 000000000..710a4e9c5 --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts @@ -0,0 +1,79 @@ +import '@angular/localize/init'; +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; + +import { GfPortfolioFilterFormComponent } from './portfolio-filter-form.component'; + +const meta: Meta = { + title: 'Portfolio Filter Form', + component: GfPortfolioFilterFormComponent, + decorators: [ + moduleMetadata({ + imports: [GfPortfolioFilterFormComponent] + }) + ] +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + accounts: [ + { + id: '733110b6-7c55-44eb-8cc5-c4c3e9d48a79', + name: 'Trading Account', + platform: { + name: 'Interactive Brokers', + url: 'https://interactivebrokers.com' + } + }, + { + id: '24ba27d6-e04b-4fb4-b856-b24c2ef0422a', + name: 'Investment Account', + platform: { + name: 'Fidelity', + url: 'https://fidelity.com' + } + } + ] as any, + assetClasses: [ + { id: 'COMMODITY', label: 'Commodity', type: 'ASSET_CLASS' }, + { id: 'EQUITY', label: 'Equity', type: 'ASSET_CLASS' }, + { id: 'FIXED_INCOME', label: 'Fixed Income', type: 'ASSET_CLASS' } + ] as any, + holdings: [ + { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Apple Inc.', + symbol: 'AAPL' + }, + { + currency: 'USD', + dataSource: 'YAHOO', + name: 'Microsoft Corporation', + symbol: 'MSFT' + } + ] as any, + tags: [ + { + id: 'EMERGENCY_FUND', + label: 'Emergency Fund', + type: 'TAG' + }, + { + id: 'RETIREMENT_FUND', + label: 'Retirement Fund', + type: 'TAG' + } + ] as any, + disabled: false + } +}; + +export const Disabled: Story = { + args: { + ...Default.args, + disabled: true + } +}; 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 new file mode 100644 index 000000000..794f43d4d --- /dev/null +++ b/libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts @@ -0,0 +1,177 @@ +import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; +import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; +import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces'; +import { AccountWithPlatform } from '@ghostfolio/common/types'; + +import { + CUSTOM_ELEMENTS_SCHEMA, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + forwardRef +} from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormGroup, + FormsModule, + NG_VALUE_ACCESSOR, + ReactiveFormsModule +} from '@angular/forms'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { Subject, takeUntil } from 'rxjs'; + +import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component'; +import { PortfolioFilterFormValue } from './interfaces'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + FormsModule, + GfEntityLogoComponent, + GfSymbolPipe, + MatFormFieldModule, + MatSelectModule, + ReactiveFormsModule + ], + providers: [ + { + multi: true, + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => GfPortfolioFilterFormComponent) + } + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + selector: 'gf-portfolio-filter-form', + styleUrls: ['./portfolio-filter-form.component.scss'], + templateUrl: './portfolio-filter-form.component.html' +}) +export class GfPortfolioFilterFormComponent + implements ControlValueAccessor, OnInit, OnChanges, OnDestroy +{ + @Input() accounts: AccountWithPlatform[] = []; + @Input() assetClasses: Filter[] = []; + @Input() holdings: PortfolioPosition[] = []; + @Input() tags: Filter[] = []; + @Input() disabled = false; + + public filterForm: FormGroup; + + private unsubscribeSubject = new Subject(); + + public constructor( + private changeDetectorRef: ChangeDetectorRef, + private formBuilder: FormBuilder + ) { + this.filterForm = this.formBuilder.group({ + account: new FormControl(null), + assetClass: new FormControl(null), + holding: new FormControl(null), + tag: new FormControl(null) + }); + } + + public ngOnInit() { + this.filterForm.valueChanges + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((value) => { + this.onChange(value as PortfolioFilterFormValue); + this.onTouched(); + }); + } + + public hasFilters() { + const formValue = this.filterForm.value; + + return Object.values(formValue).some((value) => { + return !!value; + }); + } + + public holdingComparisonFunction( + option: PortfolioPosition, + value: PortfolioPosition + ) { + if (value === null) { + return false; + } + + return ( + getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value) + ); + } + + public ngOnChanges() { + if (this.disabled) { + this.filterForm.disable({ emitEvent: false }); + } else { + this.filterForm.enable({ emitEvent: false }); + } + + const tagControl = this.filterForm.get('tag'); + + if (this.tags.length === 0) { + tagControl?.disable({ emitEvent: false }); + } else if (!this.disabled) { + tagControl?.enable({ emitEvent: false }); + } + + this.changeDetectorRef.markForCheck(); + } + + public registerOnChange(fn: (value: PortfolioFilterFormValue) => void) { + this.onChange = fn; + } + + public registerOnTouched(fn: () => void) { + this.onTouched = fn; + } + + public setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + + if (this.disabled) { + this.filterForm.disable({ emitEvent: false }); + } else { + this.filterForm.enable({ emitEvent: false }); + } + + this.changeDetectorRef.markForCheck(); + } + + public writeValue(value: PortfolioFilterFormValue | null) { + if (value) { + this.filterForm.setValue( + { + account: value.account ?? null, + assetClass: value.assetClass ?? null, + holding: value.holding ?? null, + tag: value.tag ?? null + }, + { emitEvent: false } + ); + } else { + this.filterForm.reset({}, { emitEvent: false }); + } + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private onChange = (_value: PortfolioFilterFormValue): void => { + // ControlValueAccessor onChange callback + }; + + private onTouched = (): void => { + // ControlValueAccessor onTouched callback + }; +} From b915c9554e6bec9ee2cb690a8ffaf4a8fd857e6f Mon Sep 17 00:00:00 2001 From: Dibyendu Sahoo Date: Tue, 21 Oct 2025 00:07:35 +0530 Subject: [PATCH 007/146] Task/extend rule settings interface by locale (#5802) * Extend rule settings interface by locale --- apps/api/src/models/interfaces/rule-settings.interface.ts | 1 + .../rules/account-cluster-risk/current-investment.ts | 7 ++++++- .../models/rules/account-cluster-risk/single-account.ts | 3 ++- .../src/models/rules/asset-class-cluster-risk/equity.ts | 7 ++++++- .../models/rules/asset-class-cluster-risk/fixed-income.ts | 7 ++++++- .../base-currency-current-investment.ts | 7 ++++++- .../rules/currency-cluster-risk/current-investment.ts | 7 ++++++- .../economic-market-cluster-risk/developed-markets.ts | 7 ++++++- .../rules/economic-market-cluster-risk/emerging-markets.ts | 7 ++++++- .../models/rules/emergency-fund/emergency-fund-setup.ts | 7 ++++++- .../src/models/rules/fees/fee-ratio-initial-investment.ts | 7 ++++++- apps/api/src/models/rules/liquidity/buying-power.ts | 7 ++++++- .../rules/regional-market-cluster-risk/asia-pacific.ts | 7 ++++++- .../rules/regional-market-cluster-risk/emerging-markets.ts | 7 ++++++- .../models/rules/regional-market-cluster-risk/europe.ts | 7 ++++++- .../src/models/rules/regional-market-cluster-risk/japan.ts | 7 ++++++- .../rules/regional-market-cluster-risk/north-america.ts | 7 ++++++- 17 files changed, 93 insertions(+), 16 deletions(-) diff --git a/apps/api/src/models/interfaces/rule-settings.interface.ts b/apps/api/src/models/interfaces/rule-settings.interface.ts index 377bab52b..ff22650ca 100644 --- a/apps/api/src/models/interfaces/rule-settings.interface.ts +++ b/apps/api/src/models/interfaces/rule-settings.interface.ts @@ -1,3 +1,4 @@ export interface RuleSettings { isActive: boolean; + locale: string; } 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 0601eea9a..51c808b25 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 @@ -121,9 +121,14 @@ export class AccountClusterRiskCurrentInvestment extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.5 }; 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 8890bb767..0e07a9dc6 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 @@ -72,8 +72,9 @@ export class AccountClusterRiskSingleAccount extends Rule { }); } - public getSettings({ xRayRules }: UserSettings): RuleSettings { + public getSettings({ locale, xRayRules }: UserSettings): RuleSettings { return { + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true }; } 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 dab55413e..9a6f9dacb 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 @@ -109,9 +109,14 @@ export class AssetClassClusterRiskEquity extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.82, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.78 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 f793ec16f..70cdb63c8 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 @@ -109,9 +109,14 @@ export class AssetClassClusterRiskFixedIncome extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.22, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.18 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 2c2b6c4d6..273c98e35 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 @@ -97,9 +97,14 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.5 }; 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 7ca7a2d76..fa4f80d40 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 @@ -104,9 +104,14 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.72, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.68 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 cbf9f98b7..1414b53ed 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 @@ -104,9 +104,14 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.32, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.28 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 d97805fa6..2129f438b 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 @@ -59,9 +59,14 @@ export class EmergencyFundSetup extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true }; } 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 93c9aafd3..c5448a277 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 @@ -82,9 +82,14 @@ export class FeeRatioInitialInvestment extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.01 }; diff --git a/apps/api/src/models/rules/liquidity/buying-power.ts b/apps/api/src/models/rules/liquidity/buying-power.ts index 539d0a728..70393278d 100644 --- a/apps/api/src/models/rules/liquidity/buying-power.ts +++ b/apps/api/src/models/rules/liquidity/buying-power.ts @@ -86,9 +86,14 @@ export class BuyingPower extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0 }; diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts b/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts index 5d6cc999a..1242df759 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts @@ -94,9 +94,14 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.03, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.02 diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts index fa13a9e57..8486d843b 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts @@ -96,9 +96,14 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.12, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.08 diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts b/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts index 3bbe7af84..459848db4 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts @@ -94,9 +94,14 @@ export class RegionalMarketClusterRiskEurope extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.15, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.11 diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts b/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts index 952e14795..d9c1cff6b 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts @@ -94,9 +94,14 @@ export class RegionalMarketClusterRiskJapan extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.06, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.04 diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts index f022ecff9..6180a2cc5 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts @@ -94,9 +94,14 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule { }); } - public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings { + public getSettings({ + baseCurrency, + locale, + xRayRules + }: UserSettings): Settings { return { baseCurrency, + locale, isActive: xRayRules?.[this.getKey()]?.isActive ?? true, thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.69, thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.65 From 7ee38d0067e79aef81d14facf4e810875a78d28e Mon Sep 17 00:00:00 2001 From: Ani07-05 <86768646+Ani07-05@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:10:55 +0530 Subject: [PATCH 008/146] Task/refactor Export interface to ExportResponse interface (#5805) * Refactor Export interface to ExportResponse interface --- apps/api/src/app/export/export.controller.ts | 4 ++-- apps/api/src/app/export/export.service.ts | 4 ++-- .../portfolio/calculator/portfolio-calculator-test-utils.ts | 4 ++-- .../calculator/roai/portfolio-calculator-btceur.spec.ts | 4 ++-- .../roai/portfolio-calculator-btcusd-short.spec.ts | 4 ++-- .../calculator/roai/portfolio-calculator-btcusd.spec.ts | 4 ++-- ...portfolio-calculator-novn-buy-and-sell-partially.spec.ts | 4 ++-- .../roai/portfolio-calculator-novn-buy-and-sell.spec.ts | 4 ++-- apps/client/src/app/services/data.service.ts | 4 ++-- apps/client/src/app/services/ics/ics.service.ts | 4 ++-- libs/common/src/lib/interfaces/index.ts | 4 ++-- .../export-response.interface.ts} | 6 +++--- 12 files changed, 25 insertions(+), 25 deletions(-) rename libs/common/src/lib/interfaces/{export.interface.ts => responses/export-response.interface.ts} (82%) diff --git a/apps/api/src/app/export/export.controller.ts b/apps/api/src/app/export/export.controller.ts index 8fa2baa43..0b4a2c6e0 100644 --- a/apps/api/src/app/export/export.controller.ts +++ b/apps/api/src/app/export/export.controller.ts @@ -1,7 +1,7 @@ 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 { ApiService } from '@ghostfolio/api/services/api/api.service'; -import { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -35,7 +35,7 @@ export class ExportController { @Query('dataSource') filterByDataSource?: string, @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string - ): Promise { + ): Promise { const activityIds = filterByActivityIds?.split(',') ?? []; const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 7d78bdf22..2001fd3e2 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -3,7 +3,7 @@ import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; -import { Filter, Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse, Filter } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { Platform, Prisma } from '@prisma/client'; @@ -28,7 +28,7 @@ export class ExportService { filters?: Filter[]; userCurrency: string; userId: string; - }): Promise { + }): Promise { const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => { return type; }); diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts index ccdbafac8..f4c99916f 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts @@ -1,4 +1,4 @@ -import { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { readFileSync } from 'node:fs'; @@ -39,6 +39,6 @@ export const userDummyData = { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }; -export function loadExportFile(filePath: string): Export { +export function loadExportFile(filePath: string): ExportResponse { return JSON.parse(readFileSync(filePath, 'utf8')); } 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 1ac0dcd16..ca9e5b0d5 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 @@ -15,7 +15,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 { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; @@ -52,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let exportResponse: Export; + let exportResponse: ExportResponse; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; 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 29413c6ad..3e67389dd 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 @@ -15,7 +15,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 { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; @@ -52,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let exportResponse: Export; + let exportResponse: ExportResponse; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; 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 26b3325c2..f08083554 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 @@ -15,7 +15,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 { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; @@ -52,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let exportResponse: Export; + let exportResponse: ExportResponse; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; 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 0f1cdfff7..4678dbd5e 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 @@ -15,7 +15,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 { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; @@ -52,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let exportResponse: Export; + let exportResponse: ExportResponse; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; 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 e426a68fa..c4ccab7ad 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 @@ -15,7 +15,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 { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; import { Big } from 'big.js'; @@ -52,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let exportResponse: Export; + let exportResponse: ExportResponse; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 66d84c09d..d6d582c56 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -34,7 +34,7 @@ import { BenchmarkMarketDataDetailsResponse, BenchmarkResponse, DataProviderHealthResponse, - Export, + ExportResponse, Filter, ImportResponse, InfoItem, @@ -407,7 +407,7 @@ export class DataService { params = params.append('activityIds', activityIds.join(',')); } - return this.http.get('/api/v1/export', { + return this.http.get('/api/v1/export', { params }); } diff --git a/apps/client/src/app/services/ics/ics.service.ts b/apps/client/src/app/services/ics/ics.service.ts index b94b2dee3..a3235380e 100644 --- a/apps/client/src/app/services/ics/ics.service.ts +++ b/apps/client/src/app/services/ics/ics.service.ts @@ -1,5 +1,5 @@ import { capitalize } from '@ghostfolio/common/helper'; -import { Export } from '@ghostfolio/common/interfaces'; +import { ExportResponse } from '@ghostfolio/common/interfaces'; import { Injectable } from '@angular/core'; import { Type } from '@prisma/client'; @@ -13,7 +13,7 @@ export class IcsService { private readonly ICS_LINE_BREAK = '\r\n'; public transformActivitiesToIcsContent( - aActivities: Export['activities'] + aActivities: ExportResponse['activities'] ): string { const header = [ 'BEGIN:VCALENDAR', diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 854c53df0..6c8754ea9 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -15,7 +15,6 @@ import type { Benchmark } from './benchmark.interface'; import type { Coupon } from './coupon.interface'; import type { DataProviderInfo } from './data-provider-info.interface'; import type { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; -import type { Export } from './export.interface'; import type { FilterGroup } from './filter-group.interface'; import type { Filter } from './filter.interface'; import type { FireWealth } from './fire-wealth.interface'; @@ -49,6 +48,7 @@ import type { DataProviderGhostfolioStatusResponse } from './responses/data-prov import type { DataProviderHealthResponse } from './responses/data-provider-health-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'; import type { HistoricalResponse } from './responses/historical-response.interface'; import type { ImportResponse } from './responses/import-response.interface'; import type { InfoResponse } from './responses/info-response.interface'; @@ -105,7 +105,7 @@ export { DataProviderInfo, DividendsResponse, EnhancedSymbolProfile, - Export, + ExportResponse, Filter, FilterGroup, FireWealth, diff --git a/libs/common/src/lib/interfaces/export.interface.ts b/libs/common/src/lib/interfaces/responses/export-response.interface.ts similarity index 82% rename from libs/common/src/lib/interfaces/export.interface.ts rename to libs/common/src/lib/interfaces/responses/export-response.interface.ts index 16a49b0ef..a5416e886 100644 --- a/libs/common/src/lib/interfaces/export.interface.ts +++ b/libs/common/src/lib/interfaces/responses/export-response.interface.ts @@ -7,10 +7,10 @@ import { Tag } from '@prisma/client'; -import { AccountBalance } from './account-balance.interface'; -import { MarketData } from './market-data.interface'; +import { AccountBalance } from '../account-balance.interface'; +import { MarketData } from '../market-data.interface'; -export interface Export { +export interface ExportResponse { accounts: (Omit & { balances: AccountBalance[]; })[]; From 9b51c2da5d7c0ee9986aef6e2e3296a7ec4fafe7 Mon Sep 17 00:00:00 2001 From: Harsh Santwani <96873014+HydrallHarsh@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:12:30 +0530 Subject: [PATCH 009/146] Task/improve typings of getOrderById() functionality (#5810) * Improve typings of getOrderById() functionality --- apps/api/src/app/order/order.controller.ts | 5 +++-- apps/client/src/app/services/data.service.ts | 8 +++----- libs/common/src/lib/interfaces/index.ts | 2 ++ .../interfaces/responses/activity-response.interface.ts | 3 +++ 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 libs/common/src/lib/interfaces/responses/activity-response.interface.ts diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index ffed8ac2e..86228cf2e 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 { ActivityResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; @@ -36,7 +37,7 @@ import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { CreateOrderDto } from './create-order.dto'; -import { Activities, Activity } from './interfaces/activities.interface'; +import { Activities } from './interfaces/activities.interface'; import { OrderService } from './order.service'; import { UpdateOrderDto } from './update-order.dto'; @@ -157,7 +158,7 @@ export class OrderController { public async getOrderById( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('id') id: string - ): Promise { + ): Promise { const impersonationUserId = await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.settings.settings.baseCurrency; diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index d6d582c56..549675f7f 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -9,10 +9,7 @@ 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 { - Activities, - Activity -} from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface'; 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'; @@ -27,6 +24,7 @@ import { AccessTokenResponse, AccountBalancesResponse, AccountsResponse, + ActivityResponse, AiPromptResponse, ApiKeyResponse, AssetProfileIdentifier, @@ -248,7 +246,7 @@ export class DataService { } public fetchActivity(aActivityId: string) { - return this.http.get(`/api/v1/order/${aActivityId}`).pipe( + return this.http.get(`/api/v1/order/${aActivityId}`).pipe( map((activity) => { activity.createdAt = parseISO(activity.createdAt as unknown as string); activity.date = parseISO(activity.date as unknown as string); diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 6c8754ea9..e3c2c2038 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -37,6 +37,7 @@ 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 { AccountsResponse } from './responses/accounts-response.interface'; +import type { ActivityResponse } from './responses/activity-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface'; import type { AssetResponse } from './responses/asset-response.interface'; @@ -82,6 +83,7 @@ export { AccountBalance, AccountBalancesResponse, AccountsResponse, + ActivityResponse, AdminData, AdminJobs, AdminMarketData, diff --git a/libs/common/src/lib/interfaces/responses/activity-response.interface.ts b/libs/common/src/lib/interfaces/responses/activity-response.interface.ts new file mode 100644 index 000000000..5dd338627 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/activity-response.interface.ts @@ -0,0 +1,3 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; + +export interface ActivityResponse extends Activity {} From ed364fc25f1973d1c7b5e361a41728cb12c63c6b Mon Sep 17 00:00:00 2001 From: Arshad Jamal Date: Tue, 21 Oct 2025 22:26:47 +0530 Subject: [PATCH 010/146] Bugfix/database seed (#5792) * Fix database seed * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 4 +++ package-lock.json | 66 ++++++++++++++++++++++++++++++++++++++++++----- package.json | 2 ++ prisma.config.ts | 5 +++- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08201a175..7690d90ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) - Improved the language localization for German (`de`) +### Fixed + +- Fixed an issue in the database seeding process caused by unresolved environment variables in `DATABASE_URL` + ## 2.209.0 - 2025-10-18 ### Added diff --git a/package-lock.json b/package-lock.json index 74b9936a9..16a8381b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,8 @@ "countries-list": "3.1.1", "countup.js": "2.9.0", "date-fns": "4.1.0", + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", "envalid": "8.1.0", "fuse.js": "7.1.0", "google-spreadsheet": "3.2.0", @@ -9081,6 +9083,33 @@ "rxjs": "^7.1.0" } }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/@nestjs/core": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.3.tgz", @@ -21037,9 +21066,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -21049,9 +21078,9 @@ } }, "node_modules/dotenv-expand": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", - "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" @@ -21063,6 +21092,18 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -33459,6 +33500,19 @@ "node": ">=8" } }, + "node_modules/nx/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/nx/node_modules/dotenv-expand": { "version": "11.0.7", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", diff --git a/package.json b/package.json index ff8adc51f..403ce7f7b 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,8 @@ "countries-list": "3.1.1", "countup.js": "2.9.0", "date-fns": "4.1.0", + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", "envalid": "8.1.0", "fuse.js": "7.1.0", "google-spreadsheet": "3.2.0", diff --git a/prisma.config.ts b/prisma.config.ts index 24da6d886..60597cbf1 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,7 +1,10 @@ -import 'dotenv/config'; +import { config } from 'dotenv'; +import { expand } from 'dotenv-expand'; import { join } from 'node:path'; import { defineConfig } from 'prisma/config'; +expand(config({ quiet: true })); + export default defineConfig({ migrations: { path: join('prisma', 'migrations'), From ceace870a875f1e69c4e11e9b0db482ff84e82c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?= Date: Tue, 21 Oct 2025 20:46:27 +0200 Subject: [PATCH 011/146] Task/upgrade ioredis to forfeit overriding defaults (#5813) * Upgrade ioredis to forfeit overriding defaults * Update changelog --- CHANGELOG.md | 2 ++ apps/api/src/app/app.module.ts | 1 - package-lock.json | 14 +++++++------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7690d90ed..cbd306f9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extracted the portfolio filter form of the assistant to a reusable component - Formatted the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) - Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) +- Reverted the explicit configuration of the _Redis_ address family in the job queue module - Improved the language localization for German (`de`) +- Upgraded `ioredis` from version `5.6.1` to `5.8.2` ### Fixed diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 86ceede28..5ec148558 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -71,7 +71,6 @@ import { UserModule } from './user/user.module'; BullModule.forRoot({ redis: { db: parseInt(process.env.REDIS_DB ?? '0', 10), - family: 0, host: process.env.REDIS_HOST, password: process.env.REDIS_PASSWORD, port: parseInt(process.env.REDIS_PORT ?? '6379', 10) diff --git a/package-lock.json b/package-lock.json index 16a8381b4..cc58d01ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6115,9 +6115,9 @@ } }, "node_modules/@ioredis/commands": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.0.tgz", - "integrity": "sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", "license": "MIT" }, "node_modules/@isaacs/balanced-match": { @@ -25108,12 +25108,12 @@ } }, "node_modules/ioredis": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", - "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", + "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", "license": "MIT", "dependencies": { - "@ioredis/commands": "^1.1.1", + "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", From 167cbcd5c7201106638970d57e75b286ce947c23 Mon Sep 17 00:00:00 2001 From: jjs2099 <140512982+jjs2099@users.noreply.github.com> Date: Wed, 22 Oct 2025 00:29:12 +0530 Subject: [PATCH 012/146] Bugfix/submit form of login with access token dialog with enter key press (#5751) * Fix form submit with enter key press * Update changelog --- CHANGELOG.md | 1 + .../login-with-access-token-dialog.html | 57 ++++++++++--------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd306f9c..63cbf5898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed the enter key press to submit the form of the login with access token dialog - Fixed an issue in the database seeding process caused by unresolved environment variables in `DATABASE_URL` ## 2.209.0 - 2025-10-18 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 e19d190c4..b51802caf 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 @@ -2,13 +2,14 @@
-
+ Security Token + + @if (data.hasPermissionToUseSocialLogin) { +
or
+
+ }
- @if (data.hasPermissionToUseSocialLogin) { -
or
-
- - Sign in with Google -
- }
+
Date: Wed, 22 Oct 2025 07:36:40 +0200 Subject: [PATCH 013/146] Task/introduce interface for create Stripe checkout session response (#5791) * Introduce interface --- .../subscription/subscription.controller.ts | 11 ++-- .../app/subscription/subscription.service.ts | 62 ++++++++++--------- .../user-account-membership.component.ts | 7 ++- .../pages/pricing/pricing-page.component.ts | 7 ++- apps/client/src/app/services/data.service.ts | 14 +++-- libs/common/src/lib/interfaces/index.ts | 2 + ...ipe-checkout-session-response.interface.ts | 3 + 7 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 libs/common/src/lib/interfaces/responses/create-stripe-checkout-session-response.interface.ts diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/subscription/subscription.controller.ts index 244a6b806..e1c705fdd 100644 --- a/apps/api/src/app/subscription/subscription.controller.ts +++ b/apps/api/src/app/subscription/subscription.controller.ts @@ -5,7 +5,10 @@ import { DEFAULT_LANGUAGE_CODE, PROPERTY_COUPONS } from '@ghostfolio/common/config'; -import { Coupon } from '@ghostfolio/common/interfaces'; +import { + Coupon, + CreateStripeCheckoutSessionResponse +} from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -111,11 +114,11 @@ export class SubscriptionController { @Post('stripe/checkout-session') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async createCheckoutSession( + public createStripeCheckoutSession( @Body() { couponId, priceId }: { couponId?: string; priceId: string } - ) { + ): Promise { try { - return this.subscriptionService.createCheckoutSession({ + return this.subscriptionService.createStripeCheckoutSession({ couponId, priceId, user: this.request.user diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 9574d17e9..37ab1c0f6 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -6,7 +6,10 @@ import { PROPERTY_STRIPE_CONFIG } from '@ghostfolio/common/config'; import { parseDate } from '@ghostfolio/common/helper'; -import { SubscriptionOffer } from '@ghostfolio/common/interfaces'; +import { + CreateStripeCheckoutSessionResponse, + SubscriptionOffer +} from '@ghostfolio/common/interfaces'; import { SubscriptionOfferKey, UserWithSettings @@ -38,7 +41,7 @@ export class SubscriptionService { } } - public async createCheckoutSession({ + public async createStripeCheckoutSession({ couponId, priceId, user @@ -46,7 +49,7 @@ export class SubscriptionService { couponId?: string; priceId: string; user: UserWithSettings; - }) { + }): Promise { const subscriptionOffers: { [offer in SubscriptionOfferKey]: SubscriptionOffer; } = @@ -58,33 +61,34 @@ export class SubscriptionService { } ); - const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = { - cancel_url: `${this.configurationService.get('ROOT_URL')}/${ - user.settings.settings.language - }/account`, - client_reference_id: user.id, - line_items: [ - { - price: priceId, - quantity: 1 - } - ], - locale: - (user.settings?.settings - ?.language as Stripe.Checkout.SessionCreateParams.Locale) ?? - DEFAULT_LANGUAGE_CODE, - metadata: subscriptionOffer - ? { subscriptionOffer: JSON.stringify(subscriptionOffer) } - : {}, - mode: 'payment', - payment_method_types: ['card'], - success_url: `${this.configurationService.get( - 'ROOT_URL' - )}/api/v1/subscription/stripe/callback?checkoutSessionId={CHECKOUT_SESSION_ID}` - }; + const stripeCheckoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = + { + cancel_url: `${this.configurationService.get('ROOT_URL')}/${ + user.settings.settings.language + }/account`, + client_reference_id: user.id, + line_items: [ + { + price: priceId, + quantity: 1 + } + ], + locale: + (user.settings?.settings + ?.language as Stripe.Checkout.SessionCreateParams.Locale) ?? + DEFAULT_LANGUAGE_CODE, + metadata: subscriptionOffer + ? { subscriptionOffer: JSON.stringify(subscriptionOffer) } + : {}, + mode: 'payment', + payment_method_types: ['card'], + success_url: `${this.configurationService.get( + 'ROOT_URL' + )}/api/v1/subscription/stripe/callback?checkoutSessionId={CHECKOUT_SESSION_ID}` + }; if (couponId) { - checkoutSessionCreateParams.discounts = [ + stripeCheckoutSessionCreateParams.discounts = [ { coupon: couponId } @@ -92,7 +96,7 @@ export class SubscriptionService { } const session = await this.stripe.checkout.sessions.create( - checkoutSessionCreateParams + stripeCheckoutSessionCreateParams ); return { 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 f2f63b32b..025ec0f7a 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 @@ -108,7 +108,10 @@ export class GfUserAccountMembershipComponent implements OnDestroy { public onCheckout() { this.dataService - .createCheckoutSession({ couponId: this.couponId, priceId: this.priceId }) + .createStripeCheckoutSession({ + couponId: this.couponId, + priceId: this.priceId + }) .pipe( catchError((error) => { this.notificationService.alert({ @@ -117,7 +120,7 @@ export class GfUserAccountMembershipComponent implements OnDestroy { throw error; }), - switchMap(({ sessionId }: { sessionId: string }) => { + switchMap(({ sessionId }) => { return this.stripeService.redirectToCheckout({ sessionId }); }) ) 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 8bc3e3a67..82560246f 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -134,9 +134,12 @@ export class GfPricingPageComponent implements OnDestroy, OnInit { public onCheckout() { this.dataService - .createCheckoutSession({ couponId: this.couponId, priceId: this.priceId }) + .createStripeCheckoutSession({ + couponId: this.couponId, + priceId: this.priceId + }) .pipe( - switchMap(({ sessionId }: { sessionId: string }) => { + switchMap(({ sessionId }) => { return this.stripeService.redirectToCheckout({ sessionId }); }), catchError((error) => { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 549675f7f..a32bc6d3e 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -31,6 +31,7 @@ import { AssetResponse, BenchmarkMarketDataDetailsResponse, BenchmarkResponse, + CreateStripeCheckoutSessionResponse, DataProviderHealthResponse, ExportResponse, Filter, @@ -168,17 +169,20 @@ export class DataService { return params; } - public createCheckoutSession({ + public createStripeCheckoutSession({ couponId, priceId }: { couponId?: string; priceId: string; }) { - return this.http.post('/api/v1/subscription/stripe/checkout-session', { - couponId, - priceId - }); + return this.http.post( + '/api/v1/subscription/stripe/checkout-session', + { + couponId, + priceId + } + ); } public fetchAccount(aAccountId: string) { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index e3c2c2038..828a65974 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -43,6 +43,7 @@ import type { ApiKeyResponse } from './responses/api-key-response.interface'; import type { AssetResponse } from './responses/asset-response.interface'; import type { BenchmarkMarketDataDetailsResponse } from './responses/benchmark-market-data-details-response.interface'; import type { BenchmarkResponse } from './responses/benchmark-response.interface'; +import type { CreateStripeCheckoutSessionResponse } from './responses/create-stripe-checkout-session-response.interface'; import type { DataEnhancerHealthResponse } from './responses/data-enhancer-health-response.interface'; import type { DataProviderGhostfolioAssetProfileResponse } from './responses/data-provider-ghostfolio-asset-profile-response.interface'; import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface'; @@ -100,6 +101,7 @@ export { BenchmarkProperty, BenchmarkResponse, Coupon, + CreateStripeCheckoutSessionResponse, DataEnhancerHealthResponse, DataProviderGhostfolioAssetProfileResponse, DataProviderGhostfolioStatusResponse, diff --git a/libs/common/src/lib/interfaces/responses/create-stripe-checkout-session-response.interface.ts b/libs/common/src/lib/interfaces/responses/create-stripe-checkout-session-response.interface.ts new file mode 100644 index 000000000..18c9e4400 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/create-stripe-checkout-session-response.interface.ts @@ -0,0 +1,3 @@ +export interface CreateStripeCheckoutSessionResponse { + sessionId: string; +} From f19d9c78aa8e96a1e3320ed7f751ef5f35c07b0d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:37:15 +0200 Subject: [PATCH 014/146] Task/clean up unused eslint-disable-next-line directives (#5782) * Clean up unused eslint-disable-next-line directives --- .../roai/portfolio-calculator-baln-buy-and-buy.spec.ts | 3 --- ...lio-calculator-baln-buy-and-sell-in-two-activities.spec.ts | 3 --- .../roai/portfolio-calculator-baln-buy-and-sell.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-baln-buy.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-btceur.spec.ts | 3 --- ...portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts | 4 ---- .../calculator/roai/portfolio-calculator-btcusd-short.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-btcusd.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-fee.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-googl-buy.spec.ts | 4 ---- .../calculator/roai/portfolio-calculator-liability.spec.ts | 3 --- .../roai/portfolio-calculator-msft-buy-and-sell.spec.ts | 2 -- .../roai/portfolio-calculator-msft-buy-with-dividend.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-no-orders.spec.ts | 3 --- .../portfolio-calculator-novn-buy-and-sell-partially.spec.ts | 3 --- .../roai/portfolio-calculator-novn-buy-and-sell.spec.ts | 3 --- .../calculator/roai/portfolio-calculator-valuable.spec.ts | 3 --- 17 files changed, 52 deletions(-) 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 9ffa1b409..aa174f319 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 ab8000702..69b6c3dfc 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 cc65fa8a2..a3cb8716e 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 7d9544666..ae083a7db 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 ca9e5b0d5..cef8938c2 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 @@ -23,7 +23,6 @@ import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -34,7 +33,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -44,7 +42,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 de3f5d3cd..36e6fa900 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 @@ -21,7 +21,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -32,7 +31,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -42,7 +40,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) @@ -53,7 +50,6 @@ jest.mock( '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention ExchangeRateDataService: jest.fn().mockImplementation(() => { return ExchangeRateDataServiceMock; }) 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 3e67389dd..5a4dfdc07 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 @@ -23,7 +23,6 @@ import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -34,7 +33,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -44,7 +42,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 f08083554..2ee367530 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 @@ -23,7 +23,6 @@ import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -34,7 +33,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -44,7 +42,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 aaf2c4302..002be9154 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 f7d0e9e6d..bf0b15020 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 @@ -21,7 +21,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -32,7 +31,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -42,7 +40,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) @@ -53,7 +50,6 @@ jest.mock( '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention ExchangeRateDataService: jest.fn().mockImplementation(() => { return ExchangeRateDataServiceMock; }) 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 2cb3899e9..32822014c 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 bb976564a..08015da5b 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 @@ -28,7 +28,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -38,7 +37,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 36b6e8be9..e5b128085 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts index f64328d39..fdd9e4718 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts @@ -15,7 +15,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -26,7 +25,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -36,7 +34,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 4678dbd5e..cf330d136 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 @@ -23,7 +23,6 @@ import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -34,7 +33,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -44,7 +42,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 c4ccab7ad..681169062 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 @@ -23,7 +23,6 @@ import { join } from 'node:path'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -34,7 +33,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -44,7 +42,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) 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 5e9949dd2..fc1d477a6 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 @@ -20,7 +20,6 @@ import { Big } from 'big.js'; jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention CurrentRateService: jest.fn().mockImplementation(() => { return CurrentRateServiceMock; }) @@ -31,7 +30,6 @@ jest.mock( '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention PortfolioSnapshotService: jest.fn().mockImplementation(() => { return PortfolioSnapshotServiceMock; }) @@ -41,7 +39,6 @@ jest.mock( jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { return { - // eslint-disable-next-line @typescript-eslint/naming-convention RedisCacheService: jest.fn().mockImplementation(() => { return RedisCacheServiceMock; }) From 946edd5c5a2278957f75ba16dbf764894132b600 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:40:21 +0200 Subject: [PATCH 015/146] Release 2.210.0 (#5814) --- 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 63cbf5898..a5738a998 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.210.0 - 2025-10-22 ### Added diff --git a/package-lock.json b/package-lock.json index cc58d01ca..aa32ab721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.209.0", + "version": "2.210.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.209.0", + "version": "2.210.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 403ce7f7b..397002df2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.209.0", + "version": "2.210.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 2b727f867fafbcb8dc366f8fd5e342bab36d6b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?= Date: Wed, 22 Oct 2025 14:01:36 +0200 Subject: [PATCH 016/146] Bugfix/include missing dotenv packages (#5817) * Include missing dotenv packages --- apps/api/src/dependencies.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 apps/api/src/dependencies.ts diff --git a/apps/api/src/dependencies.ts b/apps/api/src/dependencies.ts new file mode 100644 index 000000000..cd5409fd4 --- /dev/null +++ b/apps/api/src/dependencies.ts @@ -0,0 +1,3 @@ +// Dependencies required by prisma.config.ts in Docker container +import 'dotenv'; +import 'dotenv-expand'; From e096f5cc774496b7a1ca9fabfa124c1a8b36c77e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:03:47 +0200 Subject: [PATCH 017/146] Release 2.210.1 (#5818) --- 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 a5738a998..6831e9060 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.210.0 - 2025-10-22 +## 2.210.1 - 2025-10-22 ### Added diff --git a/package-lock.json b/package-lock.json index aa32ab721..24c0da20b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.210.0", + "version": "2.210.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.210.0", + "version": "2.210.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 397002df2..90d8252d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.210.0", + "version": "2.210.1", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 3cb0ca130e0f50c0f026475bf37e68be268c8b32 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:32:45 +0200 Subject: [PATCH 018/146] Task/upgrade prisma to version 6.18.0 (#5823) * Upgrade prisma to version 6.18.0 * Update changelog --- CHANGELOG.md | 8 ++++- package-lock.json | 80 +++++++++++++++++++++++------------------------ package.json | 4 +-- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6831e9060..285e8917a 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 `prisma` from version `6.17.1` to `6.18.0` + ## 2.210.1 - 2025-10-22 ### Added @@ -67,7 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed the deprecated endpoint `GET api/v1/portfolio/position/:dataSource/:symbol` - Removed the deprecated endpoint `PUT api/v1/portfolio/position/:dataSource/:symbol/tags` - Improved the language localization for German (`de`) -- Upgraded `prisma` from version `6.16.1` to `6.16.3` +- Upgraded `prisma` from version `6.16.1` to `6.17.1` ### Fixed diff --git a/package-lock.json b/package-lock.json index 24c0da20b..7889faa15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "@nestjs/schedule": "6.0.0", "@nestjs/serve-static": "5.0.3", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.17.1", + "@prisma/client": "6.18.0", "@simplewebauthn/browser": "13.1.0", "@simplewebauthn/server": "13.1.1", "@stripe/stripe-js": "7.9.0", @@ -151,7 +151,7 @@ "nx": "21.5.1", "prettier": "3.6.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.17.1", + "prisma": "6.18.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", @@ -11983,9 +11983,9 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz", - "integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz", + "integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -12005,66 +12005,66 @@ } }, "node_modules/@prisma/config": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz", - "integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", - "effect": "3.16.12", + "effect": "3.18.4", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz", - "integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz", - "integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1", - "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", - "@prisma/fetch-engine": "6.17.1", - "@prisma/get-platform": "6.17.1" + "@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" } }, "node_modules/@prisma/engines-version": { - "version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz", - "integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==", + "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==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz", - "integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==", + "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==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1", - "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", - "@prisma/get-platform": "6.17.1" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz", - "integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1" + "@prisma/debug": "6.18.0" } }, "node_modules/@redis/client": { @@ -21166,9 +21166,9 @@ "license": "MIT" }, "node_modules/effect": { - "version": "3.16.12", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz", - "integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==", + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -35802,15 +35802,15 @@ } }, "node_modules/prisma": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz", - "integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", + "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.17.1", - "@prisma/engines": "6.17.1" + "@prisma/config": "6.18.0", + "@prisma/engines": "6.18.0" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 90d8252d1..7cedd68bf 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@nestjs/schedule": "6.0.0", "@nestjs/serve-static": "5.0.3", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.17.1", + "@prisma/client": "6.18.0", "@simplewebauthn/browser": "13.1.0", "@simplewebauthn/server": "13.1.1", "@stripe/stripe-js": "7.9.0", @@ -197,7 +197,7 @@ "nx": "21.5.1", "prettier": "3.6.2", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.17.1", + "prisma": "6.18.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "8.3.0", From 37ab31ea722624f0e55c3df417424f3dc9981054 Mon Sep 17 00:00:00 2001 From: danielochinasa Date: Thu, 23 Oct 2025 16:58:14 +0100 Subject: [PATCH 019/146] Task/format value in Buying Power rule (#5824) * Format value in Buying Power rule * Update changelog --- CHANGELOG.md | 1 + apps/api/src/models/rules/liquidity/buying-power.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 285e8917a..70dc9ae17 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 +- Localized the number formatting in the static portfolio analysis rule: _Liquidity_ (Buying Power) - Upgraded `prisma` from version `6.17.1` to `6.18.0` ## 2.210.1 - 2025-10-22 diff --git a/apps/api/src/models/rules/liquidity/buying-power.ts b/apps/api/src/models/rules/liquidity/buying-power.ts index 70393278d..2cd4d6fee 100644 --- a/apps/api/src/models/rules/liquidity/buying-power.ts +++ b/apps/api/src/models/rules/liquidity/buying-power.ts @@ -40,7 +40,9 @@ export class BuyingPower extends Rule { languageCode: this.getLanguageCode(), placeholders: { baseCurrency: ruleSettings.baseCurrency, - thresholdMin: ruleSettings.thresholdMin + thresholdMin: ruleSettings.thresholdMin.toLocaleString( + ruleSettings.locale + ) } }), value: false @@ -53,7 +55,9 @@ export class BuyingPower extends Rule { languageCode: this.getLanguageCode(), placeholders: { baseCurrency: ruleSettings.baseCurrency, - thresholdMin: ruleSettings.thresholdMin + thresholdMin: ruleSettings.thresholdMin.toLocaleString( + ruleSettings.locale + ) } }), value: true From 852ed98d0fbd5e13541dd75e5afbf155f8d893ad Mon Sep 17 00:00:00 2001 From: Harsh Santwani <96873014+HydrallHarsh@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:32:59 +0530 Subject: [PATCH 020/146] Task/move prisma.config.ts to .config/prisma.ts (#5821) * Move prisma.config.ts to .config/prisma.ts * Update changelog --- .config/prisma.ts | 14 ++++++++++++++ CHANGELOG.md | 1 + Dockerfile | 4 ++-- apps/api/src/dependencies.ts | 2 +- prisma.config.ts | 14 -------------- 5 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 .config/prisma.ts delete mode 100644 prisma.config.ts diff --git a/.config/prisma.ts b/.config/prisma.ts new file mode 100644 index 000000000..64691136c --- /dev/null +++ b/.config/prisma.ts @@ -0,0 +1,14 @@ +import { defineConfig } from '@prisma/config'; +import { config } from 'dotenv'; +import { expand } from 'dotenv-expand'; +import { join } from 'node:path'; + +expand(config({ quiet: true })); + +export default defineConfig({ + migrations: { + path: join(__dirname, '..', 'prisma', 'migrations'), + seed: `node ${join(__dirname, '..', 'prisma', 'seed.mts')}` + }, + schema: join(__dirname, '..', 'prisma', 'schema.prisma') +}); diff --git a/CHANGELOG.md b/CHANGELOG.md index 70dc9ae17..d46c1d3c1 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 - Localized the number formatting in the static portfolio analysis rule: _Liquidity_ (Buying Power) +- Moved the _Prisma Configuration File_ from `prisma.config.ts` to `.config/prisma.ts` - Upgraded `prisma` from version `6.17.1` to `6.18.0` ## 2.210.1 - 2025-10-22 diff --git a/Dockerfile b/Dockerfile index be1bb53ea..5beaf6e03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,11 +13,11 @@ RUN apt-get update && apt-get install -y --no-install-suggests \ # Only add basic files without the application itself to avoid rebuilding # layers when files (package.json etc.) have not changed +COPY ./.config .config/ COPY ./CHANGELOG.md CHANGELOG.md COPY ./LICENSE LICENSE COPY ./package.json package.json COPY ./package-lock.json package-lock.json -COPY ./prisma.config.ts prisma.config.ts COPY ./prisma/schema.prisma prisma/ RUN npm install @@ -44,7 +44,7 @@ WORKDIR /ghostfolio/dist/apps/api COPY ./package-lock.json /ghostfolio/dist/apps/api/ RUN npm install -COPY prisma.config.ts /ghostfolio/dist/apps/api/ +COPY .config /ghostfolio/dist/apps/api/.config/ COPY prisma /ghostfolio/dist/apps/api/prisma/ # Overwrite the generated package.json with the original one to ensure having diff --git a/apps/api/src/dependencies.ts b/apps/api/src/dependencies.ts index cd5409fd4..acb7af382 100644 --- a/apps/api/src/dependencies.ts +++ b/apps/api/src/dependencies.ts @@ -1,3 +1,3 @@ -// Dependencies required by prisma.config.ts in Docker container +// Dependencies required by .config/prisma.ts in Docker container import 'dotenv'; import 'dotenv-expand'; diff --git a/prisma.config.ts b/prisma.config.ts deleted file mode 100644 index 60597cbf1..000000000 --- a/prisma.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { config } from 'dotenv'; -import { expand } from 'dotenv-expand'; -import { join } from 'node:path'; -import { defineConfig } from 'prisma/config'; - -expand(config({ quiet: true })); - -export default defineConfig({ - migrations: { - path: join('prisma', 'migrations'), - seed: `node ${join('prisma', 'seed.mts')}` - }, - schema: join('prisma', 'schema.prisma') -}); From 0b28da879b234bc8bb4c360857fc340e67e9339b Mon Sep 17 00:00:00 2001 From: Vansh <140736931+Vansh-Parate@users.noreply.github.com> Date: Thu, 23 Oct 2025 23:49:59 +0530 Subject: [PATCH 021/146] Task/extend export response by performanceCalculationType (#5816) * Extend export response by performanceCalculationType * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/app/export/export.controller.ts | 4 ++-- apps/api/src/app/export/export.service.ts | 19 +++++++++++++------ .../responses/export-response.interface.ts | 8 +++++++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d46c1d3c1..c249cd7d1 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 export functionality by the user account’s performance calculation type + ### Changed - Localized the number formatting in the static portfolio analysis rule: _Liquidity_ (Buying Power) diff --git a/apps/api/src/app/export/export.controller.ts b/apps/api/src/app/export/export.controller.ts index 0b4a2c6e0..5446f8789 100644 --- a/apps/api/src/app/export/export.controller.ts +++ b/apps/api/src/app/export/export.controller.ts @@ -48,8 +48,8 @@ export class ExportController { return this.exportService.export({ activityIds, filters, - userCurrency: this.request.user.settings.settings.baseCurrency, - userId: this.request.user.id + userId: this.request.user.id, + userSettings: this.request.user.settings.settings }); } } diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 2001fd3e2..d07b199be 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -3,7 +3,11 @@ import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; -import { ExportResponse, Filter } from '@ghostfolio/common/interfaces'; +import { + ExportResponse, + Filter, + UserSettings +} from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { Platform, Prisma } from '@prisma/client'; @@ -21,13 +25,13 @@ export class ExportService { public async export({ activityIds, filters, - userCurrency, - userId + userId, + userSettings }: { activityIds?: string[]; filters?: Filter[]; - userCurrency: string; userId: string; + userSettings: UserSettings; }): Promise { const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => { return type; @@ -36,11 +40,11 @@ export class ExportService { let { activities } = await this.orderService.getOrders({ filters, - userCurrency, userId, includeDrafts: true, sortColumn: 'date', sortDirection: 'asc', + userCurrency: userSettings?.baseCurrency, withExcludedAccountsAndActivities: true }); @@ -244,7 +248,10 @@ export class ExportService { } ), user: { - settings: { currency: userCurrency } + settings: { + currency: userSettings?.baseCurrency, + performanceCalculationType: userSettings?.performanceCalculationType + } } }; } diff --git a/libs/common/src/lib/interfaces/responses/export-response.interface.ts b/libs/common/src/lib/interfaces/responses/export-response.interface.ts index a5416e886..8b1697ca4 100644 --- a/libs/common/src/lib/interfaces/responses/export-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/export-response.interface.ts @@ -9,6 +9,7 @@ import { import { AccountBalance } from '../account-balance.interface'; import { MarketData } from '../market-data.interface'; +import { UserSettings } from '../user-settings.interface'; export interface ExportResponse { accounts: (Omit & { @@ -36,5 +37,10 @@ export interface ExportResponse { }; platforms: Platform[]; tags: Omit[]; - user: { settings: { currency: string } }; + user: { + settings: { + currency: UserSettings['baseCurrency']; + performanceCalculationType: UserSettings['performanceCalculationType']; + }; + }; } From a872770b7c6de31acc9fadefd988c035a76ebaf2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:54:17 +0200 Subject: [PATCH 022/146] Bugfix/footer row style of accounts table component (#5826) * Fix style of footer row * Update changelog --- CHANGELOG.md | 4 ++++ .../ui/src/lib/accounts-table/accounts-table.component.html | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c249cd7d1..0418331ea 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 - Moved the _Prisma Configuration File_ from `prisma.config.ts` to `.config/prisma.ts` - Upgraded `prisma` from version `6.17.1` to `6.18.0` +### Fixed + +- Fixed the style in the footer row of the accounts table + ## 2.210.1 - 2025-10-22 ### Added 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 609c76ee1..e9e0337c9 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -320,11 +320,7 @@ - + From 1f6b061ab006fbf4ee74aa84cac1a6257093afb8 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:13:34 -0600 Subject: [PATCH 023/146] Task/migrate tablemark to v4 (#5809) * Migrate tablemark to v4 * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/endpoints/ai/ai.service.ts | 9 +- package-lock.json | 171 ++++++++++++++------ package.json | 2 +- 4 files changed, 132 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0418331ea..09d836e9a 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 - Localized the number formatting in the static portfolio analysis rule: _Liquidity_ (Buying Power) - Moved the _Prisma Configuration File_ from `prisma.config.ts` to `.config/prisma.ts` - Upgraded `prisma` from version `6.17.1` to `6.18.0` +- Upgraded `tablemark` from version `3.1.0` to `4.1.0` ### Fixed diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index d1e1b413f..4cc4fde65 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -10,7 +10,7 @@ import type { AiPromptMode } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; import { createOpenRouter } from '@openrouter/ai-sdk-provider'; import { generateText } from 'ai'; -import tablemark, { ColumnDescriptor } from 'tablemark'; +import type { ColumnDescriptor } from 'tablemark'; @Injectable() export class AiService { @@ -92,6 +92,13 @@ export class AiService { } ); + // Dynamic import to load ESM module from CommonJS context + // eslint-disable-next-line @typescript-eslint/no-implied-eval + const dynamicImport = new Function('s', 'return import(s)') as ( + s: string + ) => Promise; + const { tablemark } = await dynamicImport('tablemark'); + const holdingsTableString = tablemark(holdingsTableRows, { columns: holdingsTableColumns }); diff --git a/package-lock.json b/package-lock.json index 7889faa15..d46062433 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ "rxjs": "7.8.1", "stripe": "18.5.0", "svgmap": "2.12.2", - "tablemark": "3.1.0", + "tablemark": "4.1.0", "twitter-api-v2": "1.23.0", "uuid": "11.1.0", "yahoo-finance2": "3.10.0", @@ -17595,6 +17595,12 @@ "node": ">=8" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "license": "MIT" + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -23695,15 +23701,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -32021,6 +32018,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -32924,6 +32922,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, "license": "MIT", "dependencies": { "lower-case": "^2.0.2", @@ -37691,17 +37690,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/sentence-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -38351,19 +38339,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/split-text-to-chunks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split-text-to-chunks/-/split-text-to-chunks-1.0.0.tgz", - "integrity": "sha512-HLtEwXK/T4l7QZSJ/kOSsZC0o5e2Xg3GzKKFxm0ZexJXw0Bo4CaEl39l7MCSRHk9EOOL5jT8JIDjmhTtcoe6lQ==", - "license": "MIT", - "dependencies": { - "get-stdin": "^5.0.1", - "minimist": "^1.2.0" - }, - "bin": { - "wordwrap": "cli.js" - } - }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -39133,16 +39108,114 @@ } }, "node_modules/tablemark": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tablemark/-/tablemark-3.1.0.tgz", - "integrity": "sha512-IwO6f0SEzp1Z+zqz/7ANUmeEac4gaNlknWyj/S9aSg11wZmWYnLeyI/xXvEOU88BYUIf8y30y0wxB58xIKrVlQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tablemark/-/tablemark-4.1.0.tgz", + "integrity": "sha512-B3LDjbDo+ac+D5RwkBOPZZ6ua8716KdT+6NO3DKOCHJq0ezE6vV2r92rjrC1ci2H+ocuysl5ytf1T0QqV65yoA==", "license": "MIT", "dependencies": { - "sentence-case": "^3.0.4", - "split-text-to-chunks": "^1.0.0" + "ansi-regex": "^6.2.2", + "change-case": "^5.4.4", + "string-width": "^8.1.0", + "wordwrapjs": "^5.1.0", + "wrap-ansi": "^9.0.2" }, "engines": { - "node": ">=14.16" + "node": ">=20" + } + }, + "node_modules/tablemark/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/tablemark/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/tablemark/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/tablemark/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tablemark/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tablemark/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/tablemark/node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/tapable": { @@ -40624,15 +40697,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -42036,6 +42100,15 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrapjs": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", + "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 7cedd68bf..dd31075cc 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "rxjs": "7.8.1", "stripe": "18.5.0", "svgmap": "2.12.2", - "tablemark": "3.1.0", + "tablemark": "4.1.0", "twitter-api-v2": "1.23.0", "uuid": "11.1.0", "yahoo-finance2": "3.10.0", From 0ec9c1dd934f34b2153aef536e492b1024537565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Fri, 24 Oct 2025 19:51:14 +0200 Subject: [PATCH 024/146] Bugfix/custom asset name rendering in import activities dialog (#5787) * Fix custom asset name rendering in import activities dialog * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/import/import.service.ts | 12 +++++++++++- apps/api/src/app/order/create-order.dto.ts | 3 ++- apps/api/src/app/order/order.service.ts | 4 ++-- .../src/app/services/import-activities.service.ts | 6 +----- .../activities-table/activities-table.component.html | 5 ++++- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09d836e9a..f1abea2e3 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 - Fixed the style in the footer row of the accounts table +- Fixed the rendering of names and symbols for custom assets in the import activities dialog ## 2.210.1 - 2025-10-22 diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 2725747aa..2ec28365e 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -539,6 +539,7 @@ export class ImportService { connectOrCreate: { create: { dataSource, + name, symbol, currency: assetProfile.currency, userId: dataSource === 'MANUAL' ? user.id : undefined @@ -746,10 +747,19 @@ export class ImportService { if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { // Skip asset profile validation for FEE, INTEREST, and LIABILITY // as these activity types don't require asset profiles + const assetProfileInImport = assetProfilesWithMarketDataDto?.find( + (profile) => { + return ( + profile.dataSource === dataSource && profile.symbol === symbol + ); + } + ); + assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })] = { currency, dataSource, - symbol + symbol, + name: assetProfileInImport?.name }; continue; diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index ba7a1d868..fb4ac32dd 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -44,7 +44,8 @@ export class CreateOrderDto { customCurrency?: string; @IsEnum(DataSource) - dataSource: DataSource; + @IsOptional() // Optional for type FEE, INTEREST, and LIABILITY (default data source is resolved in the backend) + dataSource?: DataSource; @IsISO8601() @Validate(IsAfter1970Constraint) diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 11579bbf1..2b5d14150 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -129,7 +129,7 @@ export class OrderService { const assetSubClass = data.assetSubClass; const dataSource: DataSource = 'MANUAL'; - let name: string; + let name = data.SymbolProfile.connectOrCreate.create.name; let symbol: string; if ( @@ -142,7 +142,7 @@ export class OrderService { symbol = data.SymbolProfile.connectOrCreate.create.symbol; } else { // Create custom asset profile - name = data.SymbolProfile.connectOrCreate.create.symbol; + name = name ?? data.SymbolProfile.connectOrCreate.create.symbol; symbol = uuidv4(); } diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 323f07a5b..0f2715e47 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -76,12 +76,8 @@ export class ImportActivitiesService { updateAccountBalance: false }); - if ( - dataSource === DataSource.MANUAL && - !['FEE', 'INTEREST', 'LIABILITY'].includes(type) - ) { + if (dataSource === DataSource.MANUAL) { // Create synthetic asset profile for MANUAL data source - // (except for FEE, INTEREST, and LIABILITY which don't require asset profiles) assetProfiles.push({ currency, symbol, 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 8079a6258..843832e1a 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -143,7 +143,10 @@ }
- @if (!isUUID(element.SymbolProfile?.symbol)) { + @if ( + element.SymbolProfile?.dataSource !== 'MANUAL' && + !isUUID(element.SymbolProfile?.symbol) + ) {
{{ element.SymbolProfile?.symbol | gfSymbol From 4ca65b88f9f92075de84a8896ed27af2607599ab Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:55:01 +0200 Subject: [PATCH 025/146] Release 2.211.0-beta.0 (#5829) --- 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 f1abea2e3..8dff9d6c3 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.211.0-beta.0 - 2025-10-24 ### Added diff --git a/package-lock.json b/package-lock.json index d46062433..de1be8c3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.210.1", + "version": "2.211.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.210.1", + "version": "2.211.0-beta.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index dd31075cc..6abe23cf4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.210.1", + "version": "2.211.0-beta.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 482b97ba9b8e83f16f3e8acc76c693d4f39cfa7f Mon Sep 17 00:00:00 2001 From: Harsh Santwani <96873014+HydrallHarsh@users.noreply.github.com> Date: Sat, 25 Oct 2025 00:28:44 +0530 Subject: [PATCH 026/146] Feature/set up user detail dialog in admin control panel (#5819) * Set up user detail dialog * Update changelog --- CHANGELOG.md | 1 + .../admin-users/admin-users.component.ts | 70 +++++++++++++++++-- .../components/admin-users/admin-users.html | 12 +++- .../interfaces/interfaces.ts | 7 ++ .../user-detail-dialog.component.scss | 7 ++ .../user-detail-dialog.component.ts | 52 ++++++++++++++ .../user-detail-dialog.html | 32 +++++++++ 7 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts create mode 100644 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.scss create mode 100644 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts create mode 100644 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dff9d6c3..d48751572 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 - Extended the export functionality by the user account’s performance calculation type +- Added a user detail dialog to the users section of the admin control panel ### Changed 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 84b82d111..fce97877b 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 @@ -19,6 +19,7 @@ import { ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; +import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; import { MatPaginator, @@ -26,6 +27,7 @@ import { PageEvent } from '@angular/material/paginator'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { ActivatedRoute, Router } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { differenceInSeconds, @@ -37,8 +39,10 @@ import { contractOutline, ellipsisHorizontal, keyOutline, + personOutline, trashOutline } from 'ionicons/icons'; +import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -49,6 +53,8 @@ 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: [ @@ -71,6 +77,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public dataSource = new MatTableDataSource(); public defaultDateFormat: string; + public deviceType: string; public displayedColumns: string[] = []; public getEmojiFlag = getEmojiFlag; public hasPermissionForSubscription: boolean; @@ -87,11 +94,16 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private deviceService: DeviceDetectorService, + private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, private notificationService: NotificationService, + private route: ActivatedRoute, + private router: Router, private tokenStorageService: TokenStorageService, private userService: UserService ) { + this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.info = this.dataService.fetchInfo(); this.hasPermissionForSubscription = hasPermission( @@ -121,6 +133,14 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } + this.route.queryParams + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((params) => { + if (params['userDetailDialog'] && params['userId']) { + this.openUserDetailDialog(params['userId']); + } + }); + this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((state) => { @@ -138,7 +158,13 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } }); - addIcons({ contractOutline, ellipsisHorizontal, keyOutline, trashOutline }); + addIcons({ + contractOutline, + ellipsisHorizontal, + keyOutline, + personOutline, + trashOutline + }); } public ngOnInit() { @@ -161,6 +187,12 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { return ''; } + public onChangePage(page: PageEvent) { + this.fetchUsers({ + pageIndex: page.pageIndex + }); + } + public onDeleteUser(aId: string) { this.notificationService.confirm({ confirmFn: () => { @@ -212,9 +244,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { window.location.reload(); } - public onChangePage(page: PageEvent) { - this.fetchUsers({ - pageIndex: page.pageIndex + public onOpenUserDetailDialog(userId: string) { + this.router.navigate([], { + queryParams: { userId, userDetailDialog: true } }); } @@ -245,4 +277,34 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { this.changeDetectorRef.markForCheck(); }); } + + private openUserDetailDialog(userId: string) { + const userData = this.dataSource.data.find(({ id }) => { + return id === userId; + }); + + if (!userData) { + this.router.navigate(['.'], { relativeTo: this.route }); + return; + } + + const dialogRef = this.dialog.open(GfUserDetailDialogComponent, { + autoFocus: false, + data: { + userData, + deviceType: this.deviceType, + locale: this.user?.settings?.locale + } as UserDetailDialogParams, + height: this.deviceType === 'mobile' ? '98vh' : '60vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); + + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.fetchUsers(); + this.router.navigate(['.'], { relativeTo: this.route }); + }); + } } 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 4e58abf08..e802e3272 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -216,6 +216,15 @@ + @if (hasPermissionToImpersonateAllUsers) {
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 new file mode 100644 index 000000000..81cf84d12 --- /dev/null +++ b/apps/client/src/app/components/user-detail-dialog/interfaces/interfaces.ts @@ -0,0 +1,7 @@ +import { AdminUsers } from '@ghostfolio/common/interfaces'; + +export interface UserDetailDialogParams { + deviceType: string; + locale: string; + userData: AdminUsers['users'][0]; +} diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.scss b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.scss new file mode 100644 index 000000000..b63df0134 --- /dev/null +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.scss @@ -0,0 +1,7 @@ +:host { + display: block; + + .mat-mdc-dialog-content { + max-height: unset; + } +} 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 new file mode 100644 index 000000000..bd336c4f8 --- /dev/null +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts @@ -0,0 +1,52 @@ +import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; +import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; +import { GfValueComponent } from '@ghostfolio/ui/value'; + +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA, + Inject, + OnDestroy +} 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 { UserDetailDialogParams } from './interfaces/interfaces'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'd-flex flex-column h-100' }, + imports: [ + CommonModule, + GfDialogFooterComponent, + GfDialogHeaderComponent, + GfValueComponent, + MatButtonModule, + MatDialogModule + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + selector: 'gf-user-detail-dialog', + styleUrls: ['./user-detail-dialog.component.scss'], + templateUrl: './user-detail-dialog.html' +}) +export class GfUserDetailDialogComponent implements OnDestroy { + private unsubscribeSubject = new Subject(); + + public constructor( + @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, + public dialogRef: MatDialogRef + ) {} + + public onClose() { + this.dialogRef.close(); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } +} 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 new file mode 100644 index 000000000..d90a6abf6 --- /dev/null +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -0,0 +1,32 @@ + + +
+
+
+
+ User ID +
+
+ Registration Date +
+
+
+
+ + From d1a151bd60d6306906f1c909cd899353fefc0a2f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:42:24 +0200 Subject: [PATCH 027/146] Feature/update locales (#5807) * 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 | 152 ++++++++++++++---------- apps/client/src/locales/messages.de.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.es.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.fr.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.it.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.nl.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.pl.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.pt.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.tr.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.uk.xlf | 152 ++++++++++++++---------- apps/client/src/locales/messages.xlf | 150 +++++++++++++---------- apps/client/src/locales/messages.zh.xlf | 152 ++++++++++++++---------- 13 files changed, 1031 insertions(+), 792 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d48751572..a0beadc0f 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 - Localized the number formatting in the static portfolio analysis rule: _Liquidity_ (Buying Power) - Moved the _Prisma Configuration File_ from `prisma.config.ts` to `.config/prisma.ts` +- Improved the language localization for German (`de`) - Upgraded `prisma` from version `6.17.1` to `6.18.0` - Upgraded `tablemark` from version `3.1.0` to `4.1.0` diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 3cc3c65ff..c68b369d4 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -42,7 +42,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -523,7 +523,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -559,11 +559,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -603,7 +603,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -639,7 +639,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -671,7 +671,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -831,7 +831,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -1467,7 +1467,7 @@ Està segur que vol eliminar aquest usuari? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -1503,7 +1503,7 @@ Actuar com un altre Usuari apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -1511,7 +1511,7 @@ Eliminar Usuari apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -1691,7 +1691,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1979,7 +1979,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -2015,7 +2015,7 @@ Inicieu la sessió amb la identitat d’Internet apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -2023,7 +2023,7 @@ Inicieu la sessió amb Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -2031,7 +2031,7 @@ Manteniu la sessió iniciada apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -2307,7 +2307,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -2319,7 +2319,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -2331,7 +2331,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -2343,7 +2343,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -2355,7 +2355,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -2409,6 +2409,10 @@ 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 + 12 + Granted Access @@ -2423,7 +2427,7 @@ Introduïu el vostre codi de cupó. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -2431,7 +2435,7 @@ No s’ha pogut bescanviar el codi de cupó apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -2439,7 +2443,7 @@ El codi del cupó s’ha bescanviat apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -2447,7 +2451,7 @@ Torna a carregar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -2727,7 +2731,7 @@ D’acord apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -4012,7 +4016,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -4028,7 +4032,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -4044,7 +4048,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -4103,8 +4107,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -5265,7 +5269,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -5277,7 +5281,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -5301,7 +5305,7 @@ Clonar libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -5309,7 +5313,7 @@ Exporta l’esborrany com a ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -5333,7 +5337,7 @@ Setmana fins avui libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5345,7 +5349,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5353,7 +5357,7 @@ Mes fins a la data libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5365,7 +5369,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5373,7 +5377,7 @@ Any fins a la data libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5393,7 +5397,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5405,7 +5409,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5421,7 +5425,7 @@ Interval de dates libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5429,7 +5433,7 @@ Restableix els filtres libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5437,7 +5441,7 @@ Aplicar filtres libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -5609,14 +5613,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -5651,14 +5655,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -5867,14 +5871,14 @@ Tag Etiqueta - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Year @@ -5884,6 +5888,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Years Anys @@ -5913,7 +5925,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -7071,7 +7083,7 @@ Could not generate an API key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7079,7 +7091,7 @@ Set this API key in your self-hosted environment: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7087,7 +7099,7 @@ Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7095,7 +7107,7 @@ Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7439,7 +7451,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,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 - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Follow Ghostfolio on LinkedIn diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 29ec1d9b7..2db1d100f 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -62,7 +62,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -198,11 +198,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -242,7 +242,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -278,7 +278,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -434,7 +434,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -638,7 +638,7 @@ Möchtest du diesen Benutzer wirklich löschen? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -714,7 +714,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -798,7 +798,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -834,7 +834,7 @@ Einloggen mit Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -842,7 +842,7 @@ Einloggen mit Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -850,7 +850,7 @@ Eingeloggt bleiben apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1022,7 +1022,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1034,7 +1034,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1046,7 +1046,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1058,7 +1058,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1070,7 +1070,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1078,7 +1078,7 @@ Okay apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -1146,7 +1146,7 @@ Bitte gebe deinen Gutscheincode ein. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -1154,7 +1154,7 @@ Gutscheincode konnte nicht eingelöst werden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -1162,7 +1162,7 @@ Gutscheincode wurde eingelöst apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -1170,7 +1170,7 @@ Neu laden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -1280,6 +1280,10 @@ 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 + 12 + Granted Access @@ -1382,7 +1386,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -1914,7 +1918,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1930,7 +1934,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -2202,7 +2206,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -2214,7 +2218,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -2226,7 +2230,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -2234,7 +2238,7 @@ Kopieren libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -2242,7 +2246,7 @@ Geplante Aktivität als ICS exportieren libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -2686,14 +2690,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -2720,14 +2724,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Symbol @@ -2760,14 +2764,14 @@ Tag Tag - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Cash @@ -3177,8 +3181,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -3210,7 +3214,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3726,7 +3730,7 @@ Benutzer verwenden apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -3734,7 +3738,7 @@ Benutzer löschen apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -3981,6 +3985,14 @@ 32 + + View Details + Details anzeigen + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Liabilities Verbindlichkeiten @@ -5324,7 +5336,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5660,7 +5672,7 @@ Zeitraum libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5812,7 +5824,7 @@ Seit Wochenbeginn libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5824,7 +5836,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5832,7 +5844,7 @@ Seit Monatsbeginn libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5844,7 +5856,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5852,7 +5864,7 @@ Seit Jahresbeginn libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5880,7 +5892,7 @@ Filter zurücksetzen libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5900,7 +5912,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5912,7 +5924,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5920,7 +5932,7 @@ Filter anwenden libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7095,7 +7107,7 @@ API-Schlüssel konnte nicht erstellt werden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7103,7 +7115,7 @@ Setze diesen API-Schlüssel in deiner selbst gehosteten Umgebung: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7111,7 +7123,7 @@ API-Schlüssel für den Ghostfolio Premium Datenanbieter apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7119,7 +7131,7 @@ Möchtest du wirklich einen neuen API-Schlüssel erstellen? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7463,7 +7475,7 @@ Sicherheits-Token apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7475,7 +7487,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 - 201 + 233 @@ -7483,7 +7495,7 @@ Konto, Position oder Seite finden... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7491,7 +7503,7 @@ Sicherheits-Token generieren apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registrierungsdatum + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Folge Ghostfolio auf LinkedIn diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 7d8cbd117..29746f597 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -63,7 +63,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -199,11 +199,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -243,7 +243,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -279,7 +279,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -435,7 +435,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -623,7 +623,7 @@ ¿Estás seguro de eliminar este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -699,7 +699,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -783,7 +783,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -819,7 +819,7 @@ Iniciar sesión con Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -827,7 +827,7 @@ Iniciar sesión con Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -835,7 +835,7 @@ Seguir conectado apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1007,7 +1007,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1019,7 +1019,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1031,7 +1031,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1043,7 +1043,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1055,7 +1055,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1063,7 +1063,7 @@ De acuerdo apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -1131,7 +1131,7 @@ Por favor, ingresa tu código de cupón: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -1139,7 +1139,7 @@ No se puede canjear este código de cupón apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -1147,7 +1147,7 @@ El codigo de cupón ha sido canjeado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -1155,7 +1155,7 @@ Refrescar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -1265,6 +1265,10 @@ 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 + 12 + Granted Access @@ -1367,7 +1371,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -1899,7 +1903,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1915,7 +1919,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -2187,7 +2191,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -2199,7 +2203,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -2211,7 +2215,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -2219,7 +2223,7 @@ Clonar libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -2227,7 +2231,7 @@ Exportar borrador como ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -2671,14 +2675,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -2705,14 +2709,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Symbol @@ -2745,14 +2749,14 @@ Tag Etiqueta - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Cash @@ -3162,8 +3166,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -3195,7 +3199,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3703,7 +3707,7 @@ Suplantar usuario apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -3711,7 +3715,7 @@ Eliminar usuario apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -3958,6 +3962,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Liabilities Pasivos @@ -5301,7 +5313,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5637,7 +5649,7 @@ Rango de fechas libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5789,7 +5801,7 @@ Semana hasta la fecha libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5801,7 +5813,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5809,7 +5821,7 @@ Mes hasta la fecha libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5821,7 +5833,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5829,7 +5841,7 @@ El año hasta la fecha libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5857,7 +5869,7 @@ Reiniciar filtros libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5877,7 +5889,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5889,7 +5901,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5897,7 +5909,7 @@ Aplicar filtros libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7072,7 +7084,7 @@ No se pudo generar una clave API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7080,7 +7092,7 @@ Configure esta clave API en su entorno autohospedado: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7088,7 +7100,7 @@ Clave API del proveedor de datos premium de Ghostfolio apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7096,7 +7108,7 @@ ¿Realmente desea generar una nueva clave API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7440,7 +7452,7 @@ Token de seguridad apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7452,7 +7464,7 @@ ¿Realmente deseas generar un nuevo token de seguridad para este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 201 + 233 @@ -7460,7 +7472,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7468,7 +7480,7 @@ Generar token de seguridad apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8532,6 +8544,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Siga a Ghostfolio en LinkedIn diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 62f4847eb..9b40a3031 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -54,7 +54,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -202,7 +202,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -254,11 +254,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -298,7 +298,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -334,7 +334,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -490,7 +490,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -830,7 +830,7 @@ Voulez-vous vraiment supprimer cet·te utilisateur·rice ? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -946,7 +946,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -1078,7 +1078,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -1114,7 +1114,7 @@ Se connecter avec Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -1122,7 +1122,7 @@ Se connecter avec Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -1130,7 +1130,7 @@ Rester connecté apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1242,7 +1242,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1266,7 +1266,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1278,7 +1278,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1290,7 +1290,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1302,7 +1302,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1314,7 +1314,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1358,7 +1358,7 @@ D’accord apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -1438,7 +1438,7 @@ Veuillez entrer votre code promotionnel. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -1446,7 +1446,7 @@ Le code promotionnel n’a pas pu être appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -1454,7 +1454,7 @@ Le code promotionnel a été appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -1462,7 +1462,7 @@ Rafraîchir apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -1612,6 +1612,10 @@ 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 + 12 + Granted Access @@ -2074,7 +2078,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -2662,7 +2666,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -2674,7 +2678,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -2686,7 +2690,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -2694,7 +2698,7 @@ Dupliquer libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -2702,7 +2706,7 @@ Exporter Brouillon sous ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -2818,14 +2822,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -2852,14 +2856,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -2948,14 +2952,14 @@ Tag Étiquette - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Cash @@ -3161,8 +3165,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -3194,7 +3198,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3702,7 +3706,7 @@ Voir en tant que ... apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -3710,7 +3714,7 @@ Supprimer l’Utilisateur apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -3957,6 +3961,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Liabilities Dettes @@ -5300,7 +5312,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5636,7 +5648,7 @@ Intervalle de Date libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5788,7 +5800,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5800,7 +5812,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5808,7 +5820,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5820,7 +5832,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5828,7 +5840,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5856,7 +5868,7 @@ Réinitialiser les Filtres libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5876,7 +5888,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5888,7 +5900,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5896,7 +5908,7 @@ Appliquer les Filtres libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7071,7 +7083,7 @@ Impossible de générer une clé API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7079,7 +7091,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 - 156 + 159 @@ -7087,7 +7099,7 @@ Clé API du fournisseur de données Ghostfolio Premium apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7095,7 +7107,7 @@ Voulez-vous vraiment générer une nouvelle clé API ? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7439,7 +7451,7 @@ Jeton de sécurité apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,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 - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Générer un jeton de sécurité apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Follow Ghostfolio on LinkedIn diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index d5c08f0de..f720742c8 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -63,7 +63,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -199,11 +199,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -243,7 +243,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -279,7 +279,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -435,7 +435,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -623,7 +623,7 @@ Vuoi davvero eliminare questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -699,7 +699,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -783,7 +783,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -819,7 +819,7 @@ Accedi con Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -827,7 +827,7 @@ Accedi con Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -835,7 +835,7 @@ Rimani connesso apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1007,7 +1007,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1019,7 +1019,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1031,7 +1031,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1043,7 +1043,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1055,7 +1055,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1063,7 +1063,7 @@ Bene apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -1131,7 +1131,7 @@ Inserisci il tuo codice del buono: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -1139,7 +1139,7 @@ Impossibile riscattare il codice del buono apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -1147,7 +1147,7 @@ Il codice del buono è stato riscattato apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -1155,7 +1155,7 @@ Ricarica apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -1265,6 +1265,10 @@ 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 + 12 + Granted Access @@ -1367,7 +1371,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -1899,7 +1903,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1915,7 +1919,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -2187,7 +2191,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -2199,7 +2203,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -2211,7 +2215,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -2219,7 +2223,7 @@ Clona libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -2227,7 +2231,7 @@ Esporta la bozza come ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -2671,14 +2675,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -2705,14 +2709,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Symbol @@ -2745,14 +2749,14 @@ Tag Etichetta - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Cash @@ -3162,8 +3166,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -3195,7 +3199,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3703,7 +3707,7 @@ Imita l’utente apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -3711,7 +3715,7 @@ Elimina l’utente apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -3958,6 +3962,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Liabilities Passività @@ -5301,7 +5313,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5637,7 +5649,7 @@ Intervallo di date libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5789,7 +5801,7 @@ Da inizio settimana libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5801,7 +5813,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5809,7 +5821,7 @@ Da inizio mese libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5821,7 +5833,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5829,7 +5841,7 @@ Da inizio anno libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5857,7 +5869,7 @@ Reset Filtri libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5877,7 +5889,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5889,7 +5901,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5897,7 +5909,7 @@ Applica i Filtri libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7072,7 +7084,7 @@ Non è stato possibile generare un API key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7080,7 +7092,7 @@ Imposta questa API key nel tuo ambiente self-hosted: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7088,7 +7100,7 @@ API Key for Ghostfolio Premium Data Provider apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7096,7 +7108,7 @@ Vuoi davvero generare una nuova API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7440,7 +7452,7 @@ Token di sicurezza apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7452,7 +7464,7 @@ Vuoi davvero generare un nuovo token di sicurezza per questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 201 + 233 @@ -7460,7 +7472,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7468,7 +7480,7 @@ Genera Token di Sicurezza apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8532,6 +8544,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Follow Ghostfolio on LinkedIn diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 4dd7fb278..869b932aa 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -62,7 +62,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -198,11 +198,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -242,7 +242,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -278,7 +278,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -434,7 +434,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -622,7 +622,7 @@ Wilt je deze gebruiker echt verwijderen? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -698,7 +698,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -782,7 +782,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -818,7 +818,7 @@ Aanmelden met Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -826,7 +826,7 @@ Aanmelden met Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -834,7 +834,7 @@ Aangemeld blijven apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1006,7 +1006,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1018,7 +1018,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1030,7 +1030,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1042,7 +1042,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1054,7 +1054,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1062,7 +1062,7 @@ Oké apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -1130,7 +1130,7 @@ Voer je couponcode in: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -1138,7 +1138,7 @@ Kon je kortingscode niet inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -1146,7 +1146,7 @@ Je couponcode is ingewisseld apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -1154,7 +1154,7 @@ Herladen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -1264,6 +1264,10 @@ 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 + 12 + Granted Access @@ -1366,7 +1370,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -1898,7 +1902,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1914,7 +1918,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -2186,7 +2190,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -2198,7 +2202,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -2210,7 +2214,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -2218,7 +2222,7 @@ Kloon libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -2226,7 +2230,7 @@ Concept exporteren als ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -2670,14 +2674,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -2704,14 +2708,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Symbol @@ -2744,14 +2748,14 @@ Tag Label - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Cash @@ -3161,8 +3165,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -3194,7 +3198,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3702,7 +3706,7 @@ Gebruiker immiteren apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -3710,7 +3714,7 @@ Gebruiker verwijderen apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -3957,6 +3961,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Liabilities Verplichtingen @@ -5300,7 +5312,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5636,7 +5648,7 @@ Datumbereik libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5788,7 +5800,7 @@ Week tot nu toe libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5800,7 +5812,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5808,7 +5820,7 @@ Maand tot nu toe libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5820,7 +5832,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5828,7 +5840,7 @@ Jaar tot nu toe libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5856,7 +5868,7 @@ Filters Herstellen libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5876,7 +5888,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5888,7 +5900,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5896,7 +5908,7 @@ Filters Toepassen libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7071,7 +7083,7 @@ Er kon geen API-sleutel worden gegenereerd apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7079,7 +7091,7 @@ Stel deze API-sleutel in uw zelf-gehoste omgeving in: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7087,7 +7099,7 @@ Ghostfolio Premium Gegevensleverancier API-sleutel apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7095,7 +7107,7 @@ Wilt u echt een nieuwe API-sleutel genereren? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7439,7 +7451,7 @@ Beveiligingstoken apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,7 @@ Wilt u echt een nieuw beveiligingstoken voor deze gebruiker aanmaken? apps/client/src/app/components/admin-users/admin-users.component.ts - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Beveiligingstoken Aanmaken apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Volg Ghostfolio op LinkedIn diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 1f02ab72d..87c485b25 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -263,7 +263,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -439,7 +439,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -475,11 +475,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -519,7 +519,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -555,7 +555,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -711,7 +711,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -1287,7 +1287,7 @@ Czy na pewno chcesz usunąć tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -1323,7 +1323,7 @@ Wciel się w Użytkownika apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -1331,7 +1331,7 @@ Usuń Użytkownika apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -1435,7 +1435,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -1667,7 +1667,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -1703,7 +1703,7 @@ Zaloguj się przy użyciu Tożsamości Internetowej (Internet Identity) apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -1711,7 +1711,7 @@ Zaloguj się przez Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -1719,7 +1719,7 @@ Pozostań zalogowany apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1863,7 +1863,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -2055,7 +2055,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -2067,7 +2067,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -2079,7 +2079,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -2091,7 +2091,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -2103,7 +2103,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -2135,7 +2135,7 @@ Wpisz kod kuponu: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -2143,7 +2143,7 @@ Nie udało się zrealizować kodu kuponu apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -2151,7 +2151,7 @@ Kupon został zrealizowany apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -2159,7 +2159,7 @@ Odśwież apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -2353,6 +2353,10 @@ 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 + 12 + Export Data @@ -2403,7 +2407,7 @@ Okej apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -3639,7 +3643,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -3655,7 +3659,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -3671,7 +3675,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3730,8 +3734,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -4800,7 +4804,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -4812,7 +4816,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -4828,7 +4832,7 @@ Sklonuj libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -4836,7 +4840,7 @@ Eksportuj Wersję Roboczą jako ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -5000,14 +5004,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -5042,14 +5046,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -5250,14 +5254,14 @@ Tag Tag - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Year @@ -5267,6 +5271,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Years Lata @@ -5296,7 +5308,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5636,7 +5648,7 @@ Zakres Dat libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5788,7 +5800,7 @@ Dotychczasowy tydzień libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5800,7 +5812,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5808,7 +5820,7 @@ Od początku miesiąca libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5820,7 +5832,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5828,7 +5840,7 @@ Od początku roku libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5856,7 +5868,7 @@ Resetuj Filtry libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5876,7 +5888,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5888,7 +5900,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5896,7 +5908,7 @@ Zastosuj Filtry libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7071,7 +7083,7 @@ Nie udało się wygenerować klucza API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7079,7 +7091,7 @@ Ustaw ten klucz API w samodzielnie hostowanym środowisku: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7087,7 +7099,7 @@ Klucz API dostawcy danych Premium Ghostfolio apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7095,7 +7107,7 @@ Czy na pewno chcesz wygenerować nowy klucz API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7439,7 +7451,7 @@ Token bezpieczeństwa apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,7 @@ Czy napewno chcesz wygenerować nowy token bezpieczeństwa dla tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Generowanie Tokena Zabezpieczającego apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Follow Ghostfolio on LinkedIn diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 969facd9b..8d93b9ecb 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -54,7 +54,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -202,7 +202,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -254,11 +254,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -298,7 +298,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -334,7 +334,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -490,7 +490,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -702,7 +702,7 @@ Deseja realmente excluir este utilizador? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -818,7 +818,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -958,7 +958,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -994,7 +994,7 @@ Iniciar sessão com Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -1002,7 +1002,7 @@ Iniciar sessão com Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -1010,7 +1010,7 @@ Manter sessão iniciada apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1122,7 +1122,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1254,7 +1254,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1266,7 +1266,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1278,7 +1278,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1290,7 +1290,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1302,7 +1302,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1346,7 +1346,7 @@ OK apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -1426,7 +1426,7 @@ Por favor, insira o seu código de cupão: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -1434,7 +1434,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 - 179 + 182 @@ -1442,7 +1442,7 @@ Código de cupão foi resgatado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -1450,7 +1450,7 @@ Atualizar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -1608,6 +1608,10 @@ 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 + 12 + Granted Access @@ -2050,7 +2054,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -2562,7 +2566,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -2574,7 +2578,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -2586,7 +2590,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -2594,7 +2598,7 @@ Clonar libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -2602,7 +2606,7 @@ Exportar Rascunho como ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -2690,14 +2694,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -2724,14 +2728,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Emergency Fund @@ -2792,14 +2796,14 @@ Tag Marcador - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Cash @@ -3161,8 +3165,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -3194,7 +3198,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3702,7 +3706,7 @@ Personificar Utilizador apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -3710,7 +3714,7 @@ Apagar Utilizador apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -3957,6 +3961,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Liabilities Responsabilidades @@ -5300,7 +5312,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5636,7 +5648,7 @@ Período libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5788,7 +5800,7 @@ Semana até agora libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5800,7 +5812,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5808,7 +5820,7 @@ Do mês até a data libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5820,7 +5832,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5828,7 +5840,7 @@ No acumulado do ano libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5856,7 +5868,7 @@ Redefinir filtros libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5876,7 +5888,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5888,7 +5900,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5896,7 +5908,7 @@ Aplicar filtros libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7071,7 +7083,7 @@ Não foi possível gerar uma chave de API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7079,7 +7091,7 @@ Defina esta chave de API no seu ambiente auto-hospedado: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7087,7 +7099,7 @@ Chave de API do Provedor de Dados do Ghostfolio Premium apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7095,7 +7107,7 @@ Você realmente deseja gerar uma nova chave de API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7439,7 +7451,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,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 - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Siga o Ghostfolio no LinkedIn diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 5ed44fbd1..fd87792f9 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -235,7 +235,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -399,7 +399,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -435,11 +435,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -479,7 +479,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -515,7 +515,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -671,7 +671,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -1151,7 +1151,7 @@ Bu kullanıcıyı silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -1187,7 +1187,7 @@ Kullanıcıyı Taklit Et apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -1195,7 +1195,7 @@ Kullanıcıyı Sil apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -1291,7 +1291,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -1523,7 +1523,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -1559,7 +1559,7 @@ İnternet Kimliği (Internet Identity) ile Oturum Aç apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -1567,7 +1567,7 @@ Google ile Oturum Aç apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -1575,7 +1575,7 @@ Oturumu açık tut apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1707,7 +1707,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1911,7 +1911,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1923,7 +1923,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1935,7 +1935,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1947,7 +1947,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1959,7 +1959,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -2003,7 +2003,7 @@ Tamam apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -3119,7 +3119,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -3135,7 +3135,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -3151,7 +3151,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3210,8 +3210,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -4264,7 +4264,7 @@ Lütfen kupon kodunuzu girin: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -4272,7 +4272,7 @@ Kupon kodu kullanılamadı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -4280,7 +4280,7 @@ Kupon kodu kullanıldı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -4288,7 +4288,7 @@ Yeniden Yükle apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -4470,6 +4470,10 @@ 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 + 12 + Export Data @@ -4520,7 +4524,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -4532,7 +4536,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -4548,7 +4552,7 @@ Klonla libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -4556,7 +4560,7 @@ Taslakları ICS Olarak Dışa Aktar libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -4696,14 +4700,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -4738,14 +4742,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -4946,14 +4950,14 @@ Tag Etiket - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Year @@ -4963,6 +4967,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Years Yıl @@ -5308,7 +5320,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5636,7 +5648,7 @@ Tarih Aralığı libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5788,7 +5800,7 @@ Hafta içi libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5800,7 +5812,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5808,7 +5820,7 @@ Ay içi libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5820,7 +5832,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5828,7 +5840,7 @@ Yıl içi libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5856,7 +5868,7 @@ Filtreleri Sıfırla libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5876,7 +5888,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5888,7 +5900,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5896,7 +5908,7 @@ Filtreleri Uygula libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7071,7 +7083,7 @@ API anahtarı oluşturulamadı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7079,7 +7091,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 - 156 + 159 @@ -7087,7 +7099,7 @@ Ghostfolio Premium Veri Sağlayıcı API Anahtarı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7095,7 +7107,7 @@ Yeni bir API anahtarı oluşturmak istediğinize emin misiniz? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7439,7 +7451,7 @@ Güvenlik belirteci apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,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 - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Güvenlik belirteci oluştur apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Ghostfolio’yu LinkedIn’de takip edin diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index ee2008b95..61c9be112 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -42,7 +42,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -547,7 +547,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -583,11 +583,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -627,7 +627,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -663,7 +663,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -695,7 +695,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -1503,7 +1503,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -1583,7 +1583,7 @@ Ви дійсно хочете видалити цього користувача? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -1623,7 +1623,7 @@ Видавати себе за користувача apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -1631,7 +1631,7 @@ Видалити користувача apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -1827,7 +1827,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -2111,7 +2111,7 @@ Увійти з Інтернет-Ідентичністю apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -2119,7 +2119,7 @@ Увійти з Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -2127,7 +2127,7 @@ Залишатися в системі apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -2523,7 +2523,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -2535,7 +2535,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -2547,7 +2547,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -2559,7 +2559,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -2571,7 +2571,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -2625,6 +2625,10 @@ 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 + 12 + Me @@ -2667,7 +2671,7 @@ Не вдалося згенерувати ключ API apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -2675,7 +2679,7 @@ ОК apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -2691,7 +2695,7 @@ Встановіть цей ключ API у вашому self-hosted середовищі: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -2699,7 +2703,7 @@ Ключ API Ghostfolio Premium Data Provider apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -2707,7 +2711,7 @@ Ви дійсно хочете згенерувати новий ключ API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -2715,7 +2719,7 @@ Не вдалося обміняти код купона apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -2723,7 +2727,7 @@ Код купона був обміняний apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -2731,7 +2735,7 @@ Перезавантажити apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -2739,7 +2743,7 @@ Будь ласка, введіть ваш код купона. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -4308,7 +4312,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -4324,7 +4328,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -4340,7 +4344,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -4356,7 +4360,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -4415,8 +4419,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -5995,7 +5999,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -6007,7 +6011,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -6031,7 +6035,7 @@ Клонувати libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -6039,7 +6043,7 @@ Експортувати чернетку як ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -6063,7 +6067,7 @@ Тиждень до дати libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -6075,7 +6079,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -6083,7 +6087,7 @@ Місяць до дати libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -6095,7 +6099,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -6103,7 +6107,7 @@ Рік до дати libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -6123,7 +6127,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -6135,7 +6139,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -6151,7 +6155,7 @@ Діапазон дат libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -6159,7 +6163,7 @@ Скинути фільтри libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -6167,7 +6171,7 @@ Застосувати фільтри libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -6355,14 +6359,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -6397,14 +6401,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -6713,14 +6717,14 @@ Tag Тег - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Year @@ -6730,6 +6734,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Years Роки @@ -6767,7 +6779,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -7439,7 +7451,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7451,7 +7463,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 - 201 + 233 @@ -7459,7 +7471,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7467,7 +7479,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8531,6 +8543,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn Follow Ghostfolio on LinkedIn diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 241482624..b9b3fb451 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -247,7 +247,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -420,7 +420,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -455,11 +455,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -498,7 +498,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -533,7 +533,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -674,7 +674,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -1214,7 +1214,7 @@ Do you really want to delete this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -1246,14 +1246,14 @@ Impersonate User apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 Delete User apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -1348,7 +1348,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -1559,7 +1559,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -1594,21 +1594,21 @@ Sign in with Internet Identity apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 Sign in with Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 Stay signed in apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1736,7 +1736,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -1915,7 +1915,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -1926,7 +1926,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -1937,7 +1937,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -1948,7 +1948,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -1959,7 +1959,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -1987,28 +1987,28 @@ Please enter your coupon code. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 Could not redeem coupon code apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 Coupon code has been redeemed apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 Reload apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -2181,6 +2181,10 @@ 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 + 12 + Export Data @@ -2226,7 +2230,7 @@ Okay apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -3354,7 +3358,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -3369,7 +3373,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -3384,7 +3388,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3436,8 +3440,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -4420,7 +4424,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -4431,7 +4435,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -4445,14 +4449,14 @@ Clone libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 Export Draft as ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -4613,14 +4617,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -4653,14 +4657,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -4842,14 +4846,14 @@ Tag - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Year @@ -4858,6 +4862,13 @@ 32 + + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Years @@ -4884,7 +4895,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5134,7 +5145,7 @@ Date Range libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5284,21 +5295,21 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 Week to date libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 Month to date libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5309,7 +5320,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5320,7 +5331,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5345,7 +5356,7 @@ Reset Filters libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5364,7 +5375,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5375,14 +5386,14 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 Apply Filters libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -6451,28 +6462,28 @@ Could not generate an API key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 Do you really want to generate a new API key? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 Ghostfolio Premium Data Provider API Key apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 Set this API key in your self-hosted environment: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -6767,21 +6778,21 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 201 + 233 Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -6792,7 +6803,7 @@ Generate Security Token apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -7712,6 +7723,13 @@ 128 + + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Join the Ghostfolio Slack community diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 95735836b..90e239595 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -264,7 +264,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 158 + 161 @@ -448,7 +448,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 278 + 281 @@ -484,11 +484,11 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 259 + 262 libs/ui/src/lib/activities-table/activities-table.component.html - 295 + 298 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -528,7 +528,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 440 + 443 @@ -564,7 +564,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 467 + 470 libs/ui/src/lib/benchmark/benchmark.component.html @@ -720,7 +720,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 167 + 170 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -1296,7 +1296,7 @@ 您真的要删除该用户吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 175 + 207 @@ -1332,7 +1332,7 @@ 模拟用户 apps/client/src/app/components/admin-users/admin-users.html - 223 + 232 @@ -1340,7 +1340,7 @@ 删除用户 apps/client/src/app/components/admin-users/admin-users.html - 244 + 253 @@ -1444,7 +1444,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 68 + 71 libs/common/src/lib/routes/routes.ts @@ -1676,7 +1676,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 27 + 28 apps/client/src/app/pages/landing/landing-page.html @@ -1712,7 +1712,7 @@ 使用互联网身份登录 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 37 + 38 @@ -1720,7 +1720,7 @@ 使用 Google 登录 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 47 + 48 @@ -1728,7 +1728,7 @@ 保持登录 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 56 + 59 @@ -1872,7 +1872,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 188 + 191 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -2064,7 +2064,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 383 + 364 @@ -2076,7 +2076,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -2088,7 +2088,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -2100,7 +2100,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -2112,7 +2112,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 436 + 417 @@ -2144,7 +2144,7 @@ 请输入您的优惠券代码。 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 215 + 218 @@ -2152,7 +2152,7 @@ 无法兑换优惠券代码 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 179 + 182 @@ -2160,7 +2160,7 @@ 优惠券代码已被兑换 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 192 + 195 @@ -2168,7 +2168,7 @@ 重新加载 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 193 + 196 @@ -2362,6 +2362,10 @@ 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 + 12 + Export Data @@ -2412,7 +2416,7 @@ 好的 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 154 + 157 apps/client/src/app/core/http-response.interceptor.ts @@ -3648,7 +3652,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 212 + 215 @@ -3664,7 +3668,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 377 + 380 @@ -3680,7 +3684,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 389 + 392 @@ -3739,8 +3743,8 @@ 32 - libs/ui/src/lib/assistant/assistant.html - 207 + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 26 @@ -4829,7 +4833,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 402 + 405 @@ -4841,7 +4845,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 415 + 418 @@ -4857,7 +4861,7 @@ 克隆 libs/ui/src/lib/activities-table/activities-table.component.html - 446 + 449 @@ -4865,7 +4869,7 @@ 将汇票导出为 ICS libs/ui/src/lib/activities-table/activities-table.component.html - 456 + 459 @@ -5045,14 +5049,14 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 310 + 313 - libs/ui/src/lib/assistant/assistant.html - 185 + libs/ui/src/lib/i18n.ts + 4 - libs/ui/src/lib/i18n.ts + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html 4 @@ -5087,14 +5091,14 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 290 - - libs/ui/src/lib/assistant/assistant.html - 246 - libs/ui/src/lib/i18n.ts 6 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 64 + Asset Sub Class @@ -5295,14 +5299,14 @@ Tag 标签 - - libs/ui/src/lib/assistant/assistant.html - 235 - libs/ui/src/lib/i18n.ts 31 + + libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html + 53 + Year @@ -5312,6 +5316,14 @@ 32 + + View Details + View Details + + apps/client/src/app/components/admin-users/admin-users.html + 225 + + Years @@ -5341,7 +5353,7 @@ libs/ui/src/lib/activities-table/activities-table.component.html - 236 + 239 libs/ui/src/lib/i18n.ts @@ -5621,7 +5633,7 @@ 日期范围 libs/ui/src/lib/assistant/assistant.html - 171 + 170 @@ -5789,7 +5801,7 @@ 今年迄今为止 libs/ui/src/lib/assistant/assistant.component.ts - 395 + 376 @@ -5797,7 +5809,7 @@ 本周至今 libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5805,7 +5817,7 @@ 本月至今 libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5817,7 +5829,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 391 + 372 @@ -5829,7 +5841,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 387 + 368 @@ -5857,7 +5869,7 @@ 重置过滤器 libs/ui/src/lib/assistant/assistant.html - 266 + 205 @@ -5877,7 +5889,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 405 + 386 @@ -5889,7 +5901,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 430 + 411 @@ -5897,7 +5909,7 @@ 应用过滤器 libs/ui/src/lib/assistant/assistant.html - 276 + 219 @@ -7072,7 +7084,7 @@ 无法生成 API 密钥 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 141 + 144 @@ -7080,7 +7092,7 @@ 在您的自托管环境中设置此 API 密钥: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 156 + 159 @@ -7088,7 +7100,7 @@ Ghostfolio Premium 数据提供者 API 密钥 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 159 + 162 @@ -7096,7 +7108,7 @@ 您确定要生成新的 API 密钥吗? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 164 + 167 @@ -7440,7 +7452,7 @@ 安全令牌 apps/client/src/app/components/admin-users/admin-users.component.ts - 196 + 228 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7452,7 +7464,7 @@ 您确定要为此用户生成新的安全令牌吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 201 + 233 @@ -7460,7 +7472,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 162 + 152 @@ -7468,7 +7480,7 @@ 生成安全令牌 apps/client/src/app/components/admin-users/admin-users.html - 233 + 242 @@ -8532,6 +8544,14 @@ 128 + + Registration Date + Registration Date + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 + + Follow Ghostfolio on LinkedIn 在 LinkedIn 上关注 Ghostfolio From e03f58feff376957bfbc4e39bfddfed3098fb69e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 25 Oct 2025 09:46:52 +0200 Subject: [PATCH 028/146] Task/refactor Activities interface to ActivitiesResponse interface (#5835) * Refactor Activities interface to ActivitiesResponse interface --- apps/api/src/app/order/interfaces/activities.interface.ts | 5 ----- apps/api/src/app/order/order.controller.ts | 8 +++++--- apps/api/src/app/order/order.service.ts | 5 ++--- apps/client/src/app/services/data.service.ts | 4 ++-- libs/common/src/lib/interfaces/index.ts | 2 ++ .../interfaces/responses/activities-response.interface.ts | 6 ++++++ 6 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 libs/common/src/lib/interfaces/responses/activities-response.interface.ts diff --git a/apps/api/src/app/order/interfaces/activities.interface.ts b/apps/api/src/app/order/interfaces/activities.interface.ts index 01a5a60f0..8c88cc2cf 100644 --- a/apps/api/src/app/order/interfaces/activities.interface.ts +++ b/apps/api/src/app/order/interfaces/activities.interface.ts @@ -3,11 +3,6 @@ import { AccountWithPlatform } from '@ghostfolio/common/types'; import { Order, Tag } from '@prisma/client'; -export interface Activities { - activities: Activity[]; - count: number; -} - export interface Activity extends Order { account?: AccountWithPlatform; error?: ActivityError; diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 86228cf2e..d6c231059 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -11,7 +11,10 @@ import { DATA_GATHERING_QUEUE_PRIORITY_HIGH, HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; -import { ActivityResponse } from '@ghostfolio/common/interfaces'; +import { + ActivitiesResponse, + ActivityResponse +} from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; @@ -37,7 +40,6 @@ import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { CreateOrderDto } from './create-order.dto'; -import { Activities } from './interfaces/activities.interface'; import { OrderService } from './order.service'; import { UpdateOrderDto } from './update-order.dto'; @@ -114,7 +116,7 @@ export class OrderController { @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string, @Query('take') take?: number - ): Promise { + ): Promise { let endDate: Date; let startDate: Date; diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 2b5d14150..e4c642977 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -14,6 +14,7 @@ import { } from '@ghostfolio/common/config'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { + ActivitiesResponse, AssetProfileIdentifier, EnhancedSymbolProfile, Filter @@ -37,8 +38,6 @@ import { endOfToday, isAfter } from 'date-fns'; import { groupBy, uniqBy } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { Activities } from './interfaces/activities.interface'; - @Injectable() export class OrderService { public constructor( @@ -345,7 +344,7 @@ export class OrderService { userCurrency: string; userId: string; withExcludedAccountsAndActivities?: boolean; - }): Promise { + }): Promise { let orderBy: Prisma.Enumerable = [ { date: 'asc' } ]; diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index a32bc6d3e..6f0b17ed1 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -9,7 +9,6 @@ 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 { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface'; 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'; @@ -24,6 +23,7 @@ import { AccessTokenResponse, AccountBalancesResponse, AccountsResponse, + ActivitiesResponse, ActivityResponse, AiPromptResponse, ApiKeyResponse, @@ -215,7 +215,7 @@ export class DataService { sortColumn?: string; sortDirection?: SortDirection; take?: number; - }): Observable { + }): Observable { let params = this.buildFiltersAsQueryParams({ filters }); if (range) { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 828a65974..eac5db68c 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -37,6 +37,7 @@ 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 { AccountsResponse } from './responses/accounts-response.interface'; +import type { ActivitiesResponse } from './responses/activities-response.interface'; import type { ActivityResponse } from './responses/activity-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface'; @@ -84,6 +85,7 @@ export { AccountBalance, AccountBalancesResponse, AccountsResponse, + ActivitiesResponse, ActivityResponse, AdminData, AdminJobs, diff --git a/libs/common/src/lib/interfaces/responses/activities-response.interface.ts b/libs/common/src/lib/interfaces/responses/activities-response.interface.ts new file mode 100644 index 000000000..e6abe4618 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/activities-response.interface.ts @@ -0,0 +1,6 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; + +export interface ActivitiesResponse { + activities: Activity[]; + count: number; +} From 76a2249ba4de31ce10f994cd1fed28a11738dbab Mon Sep 17 00:00:00 2001 From: Vaishnavi Parabkar Date: Sat, 25 Oct 2025 13:45:58 +0530 Subject: [PATCH 029/146] Feature/integrate SelfhostedHub into logo carousel (#5786) * Add SelfhostedHub * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/client/src/assets/images/logo-selfhostedhub.svg | 2 ++ .../src/lib/logo-carousel/logo-carousel.component.scss | 9 +++++++++ libs/ui/src/lib/logo-carousel/logo-carousel.component.ts | 6 ++++++ 4 files changed, 18 insertions(+) create mode 100644 apps/client/src/assets/images/logo-selfhostedhub.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index a0beadc0f..aa7dcb97c 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 - Extended the export functionality by the user account’s performance calculation type +- Added the _SelfhostedHub_ logo to the logo carousel on the landing page - Added a user detail dialog to the users section of the admin control panel ### Changed diff --git a/apps/client/src/assets/images/logo-selfhostedhub.svg b/apps/client/src/assets/images/logo-selfhostedhub.svg new file mode 100644 index 000000000..72fccdc07 --- /dev/null +++ b/apps/client/src/assets/images/logo-selfhostedhub.svg @@ -0,0 +1,2 @@ + + 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 18c3a26cb..89a837195 100644 --- a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss +++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss @@ -139,6 +139,15 @@ max-height: 1.25rem; } + &.logo-selfhostedhub { + background-image: url('/assets/images/logo-selfhostedhub.svg'); + background-position: center; + background-repeat: no-repeat; + background-size: contain; + filter: grayscale(1); + opacity: 0.5; + } + &.logo-sourceforge { mask-image: url('/assets/images/logo-sourceforge.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 d7d3fa6af..ea6344694 100644 --- a/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts +++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts @@ -82,6 +82,12 @@ export class GfLogoCarouselComponent { title: 'selfh.st — Self-hosted content and software', url: 'https://selfh.st' }, + { + className: 'logo-selfhostedhub', + name: 'SelfhostedHub', + title: 'SelfhostedHub — Discover best self-hosted software', + url: 'https://selfhostedhub.com' + }, { className: 'logo-sourceforge', isMask: true, From 31e234610194fe62e1dd3d800310b0f958afe0ca Mon Sep 17 00:00:00 2001 From: vitalymatyushik Date: Sat, 25 Oct 2025 10:37:57 +0200 Subject: [PATCH 030/146] Bugfix/market price in base currency during the portfolio snapshot calculation (#5828) * Add fallback for market price in base currency * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/app/portfolio/calculator/portfolio-calculator.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7dcb97c..5f49760b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the style in the footer row of the accounts table - Fixed the rendering of names and symbols for custom assets in the import activities dialog +### Fixed + +- Fixed an issue with the market price in base currency during the portfolio snapshot calculation + ## 2.210.1 - 2025-10-22 ### Added diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 3218d01f4..10e5c15cb 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -336,7 +336,7 @@ export abstract class PortfolioCalculator { ).mul( exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[ endDateString - ] + ] ?? 1 ); const { From b47a16961fa5c64c05ede984b4850a89e2e8ea37 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:40:50 +0200 Subject: [PATCH 031/146] Release 2.211.0 (#5837) --- 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 5f49760b3..d84c05f72 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.211.0-beta.0 - 2025-10-24 +## 2.211.0 - 2025-10-25 ### Added diff --git a/package-lock.json b/package-lock.json index de1be8c3c..62913d174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.211.0-beta.0", + "version": "2.211.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.211.0-beta.0", + "version": "2.211.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 6abe23cf4..512f61b6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.211.0-beta.0", + "version": "2.211.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From c394a2d5294b461c278b1cce3678806ee96a3bec Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:48:29 +0200 Subject: [PATCH 032/146] Release 2.211.0 (#5838) --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d84c05f72..1d5674673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,9 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed the style in the footer row of the accounts table - Fixed the rendering of names and symbols for custom assets in the import activities dialog - -### Fixed - - Fixed an issue with the market price in base currency during the portfolio snapshot calculation ## 2.210.1 - 2025-10-22 From f4bad6acafbecfce2fc45760f25f4c24950b7889 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:34:45 +0200 Subject: [PATCH 033/146] Bugfix/provide missing locale to rule settings dialog (#5845) * Provide locale to rule settings dialog * Update changelog --- CHANGELOG.md | 6 ++++++ apps/client/src/app/components/rule/rule.component.ts | 2 ++ apps/client/src/app/components/rules/rules.component.html | 1 + apps/client/src/app/components/rules/rules.component.ts | 1 + .../src/app/pages/portfolio/x-ray/x-ray-page.component.html | 2 ++ 5 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5674673..36c8c3c29 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 + +### Fixed + +- Ensured the locale is available in the settings dialog to customize the rule thresholds of the _X-ray_ page + ## 2.211.0 - 2025-10-25 ### Added diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index ba77ce162..a4e9f4dea 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -51,6 +51,7 @@ export class GfRuleComponent implements OnInit { @Input() categoryName: string; @Input() hasPermissionToUpdateUserSettings: boolean; @Input() isLoading: boolean; + @Input() locale: string; @Input() rule: PortfolioReportRule; @Input() settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; @@ -82,6 +83,7 @@ export class GfRuleComponent implements OnInit { data: { rule, categoryName: this.categoryName, + locale: this.locale, settings: this.settings } as RuleSettingsDialogParams, width: this.deviceType === 'mobile' ? '100vw' : '50rem' diff --git a/apps/client/src/app/components/rules/rules.component.html b/apps/client/src/app/components/rules/rules.component.html index d0cf7ece5..0c3153c52 100644 --- a/apps/client/src/app/components/rules/rules.component.html +++ b/apps/client/src/app/components/rules/rules.component.html @@ -12,6 +12,7 @@ [hasPermissionToUpdateUserSettings]=" hasPermissionToUpdateUserSettings " + [locale]="locale" [rule]="rule" [settings]="settings?.[rule.key]" (ruleUpdated)="onRuleUpdated($event)" diff --git a/apps/client/src/app/components/rules/rules.component.ts b/apps/client/src/app/components/rules/rules.component.ts index 6379a40fb..80a59740b 100644 --- a/apps/client/src/app/components/rules/rules.component.ts +++ b/apps/client/src/app/components/rules/rules.component.ts @@ -26,6 +26,7 @@ export class GfRulesComponent { @Input() categoryName: string; @Input() hasPermissionToUpdateUserSettings: boolean; @Input() isLoading: boolean; + @Input() locale: string; @Input() rules: PortfolioReportRule[]; @Input() settings: XRayRulesSettings; diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html index d4820b59e..af74137d1 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html @@ -76,6 +76,7 @@ !hasImpersonationId && hasPermissionToUpdateUserSettings " [isLoading]="isLoading" + [locale]="user?.settings?.locale" [rules]="category.rules" [settings]="user?.settings?.xRayRules" (rulesUpdated)="onRulesUpdated($event)" @@ -90,6 +91,7 @@ !hasImpersonationId && hasPermissionToUpdateUserSettings " [isLoading]="isLoading" + [locale]="user?.settings?.locale" [rules]="inactiveRules" [settings]="user?.settings?.xRayRules" (rulesUpdated)="onRulesUpdated($event)" From 54e0f5e4666e24b49dab9f325e4750b9e979633d Mon Sep 17 00:00:00 2001 From: Abhishek Singla Date: Sat, 25 Oct 2025 23:19:21 +0530 Subject: [PATCH 034/146] Feature/extend user detail dialog (#5844) * Extend user detail dialog * Update changelog --- CHANGELOG.md | 4 + .../admin-users/admin-users.component.ts | 5 +- .../interfaces/interfaces.ts | 1 + .../user-detail-dialog.html | 73 ++++++++++++++++++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c8c3c29..692029639 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 user detail dialog in the users section of the admin control panel + ### Fixed - Ensured the locale is available in the settings dialog to customize the rule thresholds of the _X-ray_ page 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 fce97877b..4c20f3fe9 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 @@ -278,9 +278,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { }); } - private openUserDetailDialog(userId: string) { + private openUserDetailDialog(aUserId: string) { const userData = this.dataSource.data.find(({ id }) => { - return id === userId; + return id === aUserId; }); if (!userData) { @@ -293,6 +293,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { data: { userData, deviceType: this.deviceType, + hasPermissionForSubscription: this.hasPermissionForSubscription, locale: this.user?.settings?.locale } as UserDetailDialogParams, height: this.deviceType === 'mobile' ? '98vh' : '60vh', 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 81cf84d12..5f3f4b87a 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 @@ -2,6 +2,7 @@ import { AdminUsers } from '@ghostfolio/common/interfaces'; export interface UserDetailDialogParams { deviceType: string; + hasPermissionForSubscription: boolean; locale: string; userData: AdminUsers['users'][0]; } 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 d90a6abf6..6bc468b59 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,9 @@
- User ID + + User ID +
Registration Date + Registration Date +
+ +
+
+ + Role + +
+ @if (data.hasPermissionForSubscription) { +
+ + Country + +
+ } +
+ +
+
+ + Accounts + +
+
+ + Activities + +
+
+ + @if (data.hasPermissionForSubscription) { +
+
+ + Engagement per Day + +
+
+ + API Requests Today + +
+
+ }
From 554710840832de15b48d4405d427209859746e04 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 26 Oct 2025 01:19:25 +0700 Subject: [PATCH 035/146] Feature/add close holding button to holding detail dialog (#5832) * Add close holding button to holding detail dialog * Update changelog --- CHANGELOG.md | 1 + .../holding-detail-dialog.component.ts | 34 +++++++++++++++++++ .../holding-detail-dialog.html | 33 ++++++++++++------ 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 692029639..990398b49 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 +- Added a close holding button to the holding detail dialog - Extended the user detail dialog in the users section of the admin control panel ### Fixed 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 d4c1c59c1..93005c11f 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,3 +1,4 @@ +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'; @@ -57,6 +58,7 @@ import { isUUID } from 'class-validator'; import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { addIcons } from 'ionicons'; import { + arrowDownCircleOutline, createOutline, flagOutline, readerOutline, @@ -167,6 +169,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { private userService: UserService ) { addIcons({ + arrowDownCircleOutline, createOutline, flagOutline, readerOutline, @@ -557,6 +560,37 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.dialogRef.close(); } + public onCloseHolding() { + const today = new Date(); + + const activity: CreateOrderDto = { + accountId: this.accounts.length === 1 ? this.accounts[0].id : null, + comment: null, + currency: this.SymbolProfile.currency, + dataSource: this.SymbolProfile.dataSource, + date: today.toISOString(), + fee: 0, + quantity: this.quantity, + symbol: this.SymbolProfile.symbol, + tags: this.tags.map(({ id }) => { + return id; + }), + type: 'SELL', + unitPrice: this.marketPrice + }; + + this.dataService + .postOrder(activity) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.router.navigate( + internalRoutes.portfolio.subRoutes.activities.routerLink + ); + + this.dialogRef.close(); + }); + } + public onExport() { const activityIds = this.dataSource.data.map(({ id }) => { return id; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 298692303..b0e462a96 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -428,6 +428,29 @@
+ @if (data.hasPermissionToCreateActivity && quantity > 0) { + + } + @if ( + dataSource?.data.length > 0 && + data.hasPermissionToReportDataGlitch === true + ) { + Report Data Glitch... + } @if (data.hasPermissionToAccessAdminControl) { ... } - @if ( - dataSource?.data.length > 0 && - data.hasPermissionToReportDataGlitch === true - ) { - Report Data Glitch... - }
From 6a93d8c050cc6ef10df9c79e794908cc73b18fc8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:41:18 +0200 Subject: [PATCH 036/146] Feature/update locales (#5847) * 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 | 4 ++ apps/client/src/locales/messages.ca.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.de.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.es.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.fr.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.it.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.nl.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.pl.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.pt.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.tr.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.uk.xlf | 76 +++++++++++++++++++++---- apps/client/src/locales/messages.xlf | 69 ++++++++++++++++++---- apps/client/src/locales/messages.zh.xlf | 76 +++++++++++++++++++++---- 13 files changed, 789 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 990398b49..1024336b3 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 - Added a close holding button to the holding detail dialog - Extended the user detail dialog in the users section of the admin control panel +### Changed + +- Improved the language localization for German (`de`) + ### Fixed - Ensured the locale is available in the settings dialog to customize the rule thresholds of the _X-ray_ page diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index c68b369d4..d7e986436 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -1366,6 +1366,14 @@ 200
+ + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Add Platform Afegeix Plataforma @@ -1739,7 +1747,7 @@ Informar d’un Problema amb les Dades apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -2409,10 +2417,6 @@ 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 - 12 - Granted Access @@ -4451,6 +4455,14 @@ 91 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Rendiment absolut dels actius @@ -5112,6 +5124,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Pla gratuït @@ -6565,7 +6585,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6668,6 +6688,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Yes @@ -6679,6 +6707,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6972,6 +7004,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Guides @@ -7110,6 +7150,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key API Key @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Default Market Price @@ -8128,7 +8184,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Average Unit Price apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 2db1d100f..4d7820831 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -633,6 +633,14 @@ 200 + + Activities + Aktivitäten + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Do you really want to delete this user? Möchtest du diesen Benutzer wirklich löschen? @@ -982,7 +990,7 @@ Datenfehler melden apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -1280,10 +1288,6 @@ 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 - 12 - Granted Access @@ -4313,6 +4317,14 @@ 210 + + User ID + Benutzer ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Kostenlose Nutzung @@ -5771,6 +5783,14 @@ 364 + + Close Holding + Position abschliessen + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Absolute Anlage Performance @@ -6589,7 +6609,7 @@ Inaktiv apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6692,6 +6712,14 @@ 11 + + Role + Rolle + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Ja @@ -6703,6 +6731,10 @@ Accounts Konten + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6996,6 +7028,14 @@ 293 + + Engagement per Day + Engagement pro Tag + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Ratgeber @@ -7134,6 +7174,14 @@ 167 + + Country + Land + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key API-Schlüssel @@ -7282,6 +7330,14 @@ 234 + + API Requests Today + Heutige API Anfragen + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Standardmarktpreis @@ -8128,7 +8184,7 @@ Anlageprofil verwalten apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Ø Preis pro Einheit apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registrierungsdatum apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 29746f597..5c064d234 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -618,6 +618,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Do you really want to delete this user? ¿Estás seguro de eliminar este usuario? @@ -967,7 +975,7 @@ Reporta un anomalía de los datos apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -1265,10 +1273,6 @@ 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 - 12 - Granted Access @@ -4290,6 +4294,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Plan gratuito @@ -5748,6 +5760,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Rendimiento absoluto de los activos @@ -6566,7 +6586,7 @@ Inactiva apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6669,6 +6689,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes @@ -6680,6 +6708,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6973,6 +7005,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Guías @@ -7111,6 +7151,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key Clave API @@ -7259,6 +7307,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Precio de mercado por defecto @@ -8129,7 +8185,7 @@ Gestionar perfil de activo apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8153,7 +8209,7 @@ Precio medio por unidad apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8544,12 +8600,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 9b40a3031..a616256d1 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -825,6 +825,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Do you really want to delete this user? Voulez-vous vraiment supprimer cet·te utilisateur·rice ? @@ -1254,7 +1262,7 @@ Signaler une Erreur de Données apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -1612,10 +1620,6 @@ 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 - 12 - Granted Access @@ -4289,6 +4293,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Plan gratuit @@ -5747,6 +5759,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Performance des Actifs en valeur absolue @@ -6565,7 +6585,7 @@ Inactif apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6668,6 +6688,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Oui @@ -6679,6 +6707,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6972,6 +7004,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Guides @@ -7110,6 +7150,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key Clé API @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Prix du marché par défaut @@ -8128,7 +8184,7 @@ Gérer le profil d’actif apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Average Unit Price apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index f720742c8..f65b225f4 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -618,6 +618,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Do you really want to delete this user? Vuoi davvero eliminare questo utente? @@ -967,7 +975,7 @@ Segnala un’anomalia dei dati apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -1265,10 +1273,6 @@ 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 - 12 - Granted Access @@ -4290,6 +4294,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Piano gratuito @@ -5748,6 +5760,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Rendimento assoluto dell’Asset @@ -6566,7 +6586,7 @@ Inattivo apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6669,6 +6689,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Si @@ -6680,6 +6708,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6973,6 +7005,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Guide @@ -7111,6 +7151,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key API Key @@ -7259,6 +7307,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Prezzo di mercato predefinito @@ -8129,7 +8185,7 @@ Gestisci profilo risorsa apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8153,7 +8209,7 @@ Average Unit Price apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8544,12 +8600,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 869b932aa..adf4bd27b 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -617,6 +617,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Do you really want to delete this user? Wilt je deze gebruiker echt verwijderen? @@ -966,7 +974,7 @@ Gegevensstoring melden apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -1264,10 +1272,6 @@ 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 - 12 - Granted Access @@ -4289,6 +4293,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Gratis abonnement @@ -5747,6 +5759,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Absolute Activaprestaties @@ -6565,7 +6585,7 @@ Inactief apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6668,6 +6688,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Ja @@ -6679,6 +6707,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6972,6 +7004,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Gidsen @@ -7110,6 +7150,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key API-sleutel @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Standaard Marktprijs @@ -8128,7 +8184,7 @@ Beheer activaprofiel apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Gemiddelde eenheidsprijs apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 87c485b25..378b0a81a 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -1166,6 +1166,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Add Platform Dodaj Platformę @@ -1875,7 +1883,7 @@ Zgłoś Błąd Danych apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -2353,10 +2361,6 @@ 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 - 12 - Export Data @@ -4643,6 +4647,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Plan Darmowy @@ -5747,6 +5759,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Łączny wynik aktywów @@ -6565,7 +6585,7 @@ Nieaktywny apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6668,6 +6688,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Tak @@ -6679,6 +6707,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6972,6 +7004,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Poradniki @@ -7110,6 +7150,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key Klucz API @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Domyślna cena rynkowa @@ -8128,7 +8184,7 @@ Zarządzaj profilem aktywów apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Średnia cena jednostkowa apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 8d93b9ecb..6a652b5c6 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -697,6 +697,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Do you really want to delete this user? Deseja realmente excluir este utilizador? @@ -1214,7 +1222,7 @@ Dados do Relatório com Problema apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -1608,10 +1616,6 @@ 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 - 12 - Granted Access @@ -4289,6 +4293,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Plano gratuito @@ -5747,6 +5759,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Desempenho absoluto de ativos @@ -6565,7 +6585,7 @@ Inativo apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6668,6 +6688,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Sim @@ -6679,6 +6707,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6972,6 +7004,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Guias @@ -7110,6 +7150,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key Chave de API @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Preço de mercado padrão @@ -8128,7 +8184,7 @@ Gerenciar perfil de ativos apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Preço médio unitário apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index fd87792f9..d0dd4191d 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -1078,6 +1078,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Add Platform Platform Ekle @@ -1731,7 +1739,7 @@ Rapor Veri Sorunu apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -4131,6 +4139,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan Ücretsiz Plan @@ -4470,10 +4486,6 @@ 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 - 12 - Export Data @@ -5747,6 +5759,14 @@ 364 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Mutlak Varlık Performansı @@ -6565,7 +6585,7 @@ Pasif apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6668,6 +6688,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Evet @@ -6679,6 +6707,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6972,6 +7004,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Kılavuzlar @@ -7110,6 +7150,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key API Anahtarı @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Varsayılan Piyasa Fiyatı @@ -8128,7 +8184,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Average Unit Price apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 61c9be112..6698f404a 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -345,6 +345,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -1362,6 +1366,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Add Platform Додати платформу @@ -1875,7 +1887,7 @@ Повідомити про збій даних apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -2625,10 +2637,6 @@ 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 - 12 - Me @@ -4779,6 +4787,14 @@ 91 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance Абсолютна прибутковість активів @@ -4936,7 +4952,7 @@ Неактивний apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -5255,6 +5271,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides Посібники @@ -5786,6 +5810,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + can be used anonymously може використовуватися анонімно @@ -6750,6 +6782,14 @@ 33 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes Так @@ -7166,6 +7206,14 @@ 110 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key Ключ API @@ -7258,6 +7306,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price Default Market Price @@ -8128,7 +8184,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8152,7 +8208,7 @@ Average Unit Price apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8543,12 +8599,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index b9b3fb451..1fb1b659d 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -1106,6 +1106,13 @@ 200 + + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Add Platform @@ -1747,7 +1754,7 @@ Report Data Glitch apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -2181,10 +2188,6 @@ 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 - 12 - Export Data @@ -4261,6 +4264,13 @@ 210 + + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan @@ -5233,6 +5243,13 @@ 193 + + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance @@ -6000,6 +6017,13 @@ 9 + + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes @@ -6018,7 +6042,7 @@ Inactive apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6136,6 +6160,10 @@ Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6361,6 +6389,13 @@ 291 + + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides @@ -6444,6 +6479,13 @@ 26 + + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key @@ -6600,6 +6642,13 @@ 450 + + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price @@ -7358,7 +7407,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -7379,7 +7428,7 @@ Average Unit Price apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -7723,11 +7772,11 @@ 128 - + Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 90e239595..bb136c783 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -1175,6 +1175,14 @@ 200 + + Activities + Activities + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 61 + + Add Platform 添加平台 @@ -1884,7 +1892,7 @@ 报告数据故障 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 452 + 450 @@ -2362,10 +2370,6 @@ 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 - 12 - Export Data @@ -4652,6 +4656,14 @@ 210 + + User ID + User ID + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 12 + + Free Plan 免费计划 @@ -5732,6 +5744,14 @@ 193 + + Close Holding + Close Holding + + apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html + 441 + + Absolute Asset Performance 绝对资产回报 @@ -6566,7 +6586,7 @@ 非活跃 apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html - 87 + 88 @@ -6669,6 +6689,14 @@ 11 + + Role + Role + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 31 + + Yes @@ -6680,6 +6708,10 @@ Accounts Accounts + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 51 + libs/ui/src/lib/assistant/assistant.html 84 @@ -6973,6 +7005,14 @@ 293 + + Engagement per Day + Engagement per Day + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 76 + + Guides 指南 @@ -7111,6 +7151,14 @@ 167 + + Country + Country + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 37 + + API Key API 密钥 @@ -7259,6 +7307,14 @@ 234 + + API Requests Today + API Requests Today + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 86 + + Default Market Price 默认市场价格 @@ -8129,7 +8185,7 @@ 管理资产概况 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 465 @@ -8153,7 +8209,7 @@ 平均单位价格 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts - 111 + 113 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -8544,12 +8600,12 @@ 128 - + Registration Date Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 22 + 23 From ecc35c9ffaae297e607d650cbe26c54a4fae2430 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 26 Oct 2025 08:59:36 +0100 Subject: [PATCH 037/146] Task/improve typings of dialogs (#5846) * Improve typings --- apps/client/src/app/app.component.ts | 7 +- .../admin-market-data.component.ts | 27 ++++--- .../admin-platform.component.ts | 62 +++++++++------- .../admin-tag/admin-tag.component.ts | 14 +++- .../interfaces/interfaces.ts | 2 +- .../admin-users/admin-users.component.ts | 7 +- .../app/components/header/header.component.ts | 6 +- .../home-watchlist.component.ts | 20 ++--- .../interfaces/interfaces.ts | 5 ++ ...ogin-with-access-token-dialog.component.ts | 4 +- .../src/app/components/rule/rule.component.ts | 7 +- .../user-account-access.component.ts | 15 +++- .../pages/accounts/accounts-page.component.ts | 25 +++++-- .../interfaces/interfaces.ts | 2 +- .../activities/activities-page.component.ts | 73 ++++++++++--------- .../interfaces/interfaces.ts | 1 - .../interfaces/interfaces.ts | 2 +- .../allocations/allocations-page.component.ts | 7 +- .../pages/register/register-page.component.ts | 22 +++--- .../src/app/services/user/user.service.ts | 22 +++--- .../src/lib/benchmark/benchmark.component.ts | 7 +- ...historical-market-data-editor.component.ts | 28 +++---- 22 files changed, 221 insertions(+), 144 deletions(-) create mode 100644 apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index bddd7d3da..5ecb7bf8b 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -276,7 +276,10 @@ export class AppComponent implements OnDestroy, OnInit { .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, { + const dialogRef = this.dialog.open< + GfHoldingDetailDialogComponent, + HoldingDetailDialogParams + >(GfHoldingDetailDialogComponent, { autoFocus: false, data: { dataSource, @@ -302,7 +305,7 @@ export class AppComponent implements OnDestroy, OnInit { hasPermission(this.user?.permissions, permissions.updateOrder) && !this.user?.settings?.isRestrictedView, locale: this.user?.settings?.locale - } as HoldingDetailDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); 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 e907f4b03..2b96bda3b 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 @@ -430,7 +430,10 @@ export class GfAdminMarketDataComponent .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open(GfAssetProfileDialogComponent, { + const dialogRef = this.dialog.open< + GfAssetProfileDialogComponent, + AssetProfileDialogParams + >(GfAssetProfileDialogComponent, { autoFocus: false, data: { dataSource, @@ -438,7 +441,7 @@ export class GfAdminMarketDataComponent colorScheme: this.user?.settings.colorScheme, deviceType: this.deviceType, locale: this.user?.settings?.locale - } as AssetProfileDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); @@ -465,17 +468,17 @@ export class GfAdminMarketDataComponent .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfCreateAssetProfileDialogComponent, - { - autoFocus: false, - data: { - deviceType: this.deviceType, - locale: this.user?.settings?.locale - } as CreateAssetProfileDialogParams, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + CreateAssetProfileDialogParams + >(GfCreateAssetProfileDialogComponent, { + autoFocus: false, + data: { + deviceType: this.deviceType, + locale: this.user?.settings?.locale + }, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() 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 845c7f375..6c95cee0b 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 @@ -34,6 +34,7 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, takeUntil } from 'rxjs'; import { GfCreateOrUpdatePlatformDialogComponent } from './create-or-update-platform-dialog/create-or-update-platform-dialog.component'; +import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform-dialog/interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -153,19 +154,20 @@ export class GfAdminPlatformComponent implements OnInit, OnDestroy { } private openCreatePlatformDialog() { - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfCreateOrUpdatePlatformDialogComponent, - { - data: { - platform: { - name: null, - url: null - } - }, - height: this.deviceType === 'mobile' ? '98vh' : undefined, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + CreateOrUpdatePlatformDialogParams + >(GfCreateOrUpdatePlatformDialogComponent, { + data: { + platform: { + id: null, + name: null, + url: null + } + }, + height: this.deviceType === 'mobile' ? '98vh' : undefined, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() @@ -191,21 +193,29 @@ export class GfAdminPlatformComponent implements OnInit, OnDestroy { }); } - private openUpdatePlatformDialog({ id, name, url }) { - const dialogRef = this.dialog.open( + private openUpdatePlatformDialog({ + id, + name, + url + }: { + id: string; + name: string; + url: string; + }) { + const dialogRef = this.dialog.open< GfCreateOrUpdatePlatformDialogComponent, - { - data: { - platform: { - id, - name, - url - } - }, - height: this.deviceType === 'mobile' ? '98vh' : undefined, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + CreateOrUpdatePlatformDialogParams + >(GfCreateOrUpdatePlatformDialogComponent, { + data: { + platform: { + id, + name, + url + } + }, + height: this.deviceType === 'mobile' ? '98vh' : undefined, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() 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 de4c8cedc..5552fa01b 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 @@ -32,6 +32,7 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, takeUntil } from 'rxjs'; import { GfCreateOrUpdateTagDialogComponent } from './create-or-update-tag-dialog/create-or-update-tag-dialog.component'; +import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -149,9 +150,13 @@ export class GfAdminTagComponent implements OnInit, OnDestroy { } private openCreateTagDialog() { - const dialogRef = this.dialog.open(GfCreateOrUpdateTagDialogComponent, { + const dialogRef = this.dialog.open< + GfCreateOrUpdateTagDialogComponent, + CreateOrUpdateTagDialogParams + >(GfCreateOrUpdateTagDialogComponent, { data: { tag: { + id: null, name: null } }, @@ -183,8 +188,11 @@ export class GfAdminTagComponent implements OnInit, OnDestroy { }); } - private openUpdateTagDialog({ id, name }) { - const dialogRef = this.dialog.open(GfCreateOrUpdateTagDialogComponent, { + private openUpdateTagDialog({ id, name }: { id: string; name: string }) { + const dialogRef = this.dialog.open< + GfCreateOrUpdateTagDialogComponent, + CreateOrUpdateTagDialogParams + >(GfCreateOrUpdateTagDialogComponent, { data: { tag: { id, diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts index bd7214786..4b7f83e93 100644 --- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts @@ -1,5 +1,5 @@ import { Tag } from '@prisma/client'; export interface CreateOrUpdateTagDialogParams { - tag: Tag; + tag: Pick; } 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 4c20f3fe9..fba54b0bb 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 @@ -288,14 +288,17 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { return; } - const dialogRef = this.dialog.open(GfUserDetailDialogComponent, { + const dialogRef = this.dialog.open< + GfUserDetailDialogComponent, + UserDetailDialogParams + >(GfUserDetailDialogComponent, { autoFocus: false, data: { userData, deviceType: this.deviceType, hasPermissionForSubscription: this.hasPermissionForSubscription, locale: this.user?.settings?.locale - } as UserDetailDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : '60vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 6e1a909a1..3f011fec4 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -1,4 +1,5 @@ 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'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; @@ -271,7 +272,10 @@ export class GfHeaderComponent implements OnChanges { } public openLoginDialog() { - const dialogRef = this.dialog.open(GfLoginWithAccessTokenDialogComponent, { + const dialogRef = this.dialog.open< + GfLoginWithAccessTokenDialogComponent, + LoginWithAccessTokenDialogParams + >(GfLoginWithAccessTokenDialogComponent, { autoFocus: false, data: { accessToken: '', 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 4c0b614c0..ab43e54dd 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 @@ -149,17 +149,17 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfCreateWatchlistItemDialogComponent, - { - autoFocus: false, - data: { - deviceType: this.deviceType, - locale: this.user?.settings?.locale - } as CreateWatchlistItemDialogParams, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + CreateWatchlistItemDialogParams + >(GfCreateWatchlistItemDialogComponent, { + autoFocus: false, + data: { + deviceType: this.deviceType, + locale: this.user?.settings?.locale + }, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() 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 new file mode 100644 index 000000000..2fa8b7ea4 --- /dev/null +++ b/apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts @@ -0,0 +1,5 @@ +export interface LoginWithAccessTokenDialogParams { + accessToken: string; + hasPermissionToUseSocialLogin: boolean; + title: string; +} 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 3812a18b9..aaca03594 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 @@ -26,6 +26,8 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { eyeOffOutline, eyeOutline } from 'ionicons/icons'; +import { LoginWithAccessTokenDialogParams } from './interfaces/interfaces'; + @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ @@ -51,7 +53,7 @@ export class GfLoginWithAccessTokenDialogComponent { public isAccessTokenHidden = true; public constructor( - @Inject(MAT_DIALOG_DATA) public data: any, + @Inject(MAT_DIALOG_DATA) public data: LoginWithAccessTokenDialogParams, public dialogRef: MatDialogRef, private internetIdentityService: InternetIdentityService, private router: Router, diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index a4e9f4dea..5ed39d5be 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -79,13 +79,16 @@ export class GfRuleComponent implements OnInit { } public onCustomizeRule(rule: PortfolioReportRule) { - const dialogRef = this.dialog.open(GfRuleSettingsDialogComponent, { + const dialogRef = this.dialog.open< + GfRuleSettingsDialogComponent, + RuleSettingsDialogParams + >(GfRuleSettingsDialogComponent, { data: { rule, categoryName: this.categoryName, locale: this.locale, settings: this.settings - } as RuleSettingsDialogParams, + }, width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); 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 bdb9af6ed..afcb9d9c8 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 @@ -31,6 +31,7 @@ import { EMPTY, Subject } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; import { GfCreateOrUpdateAccessDialogComponent } from './create-or-update-access-dialog/create-or-update-access-dialog.component'; +import { CreateOrUpdateAccessDialogParams } from './create-or-update-access-dialog/interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -187,10 +188,15 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { } private openCreateAccessDialog() { - const dialogRef = this.dialog.open(GfCreateOrUpdateAccessDialogComponent, { + const dialogRef = this.dialog.open< + GfCreateOrUpdateAccessDialogComponent, + CreateOrUpdateAccessDialogParams + >(GfCreateOrUpdateAccessDialogComponent, { data: { access: { alias: '', + grantee: null, + id: null, permissions: ['READ_RESTRICTED'], type: 'PRIVATE' } @@ -219,12 +225,15 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { return; } - const dialogRef = this.dialog.open(GfCreateOrUpdateAccessDialogComponent, { + const dialogRef = this.dialog.open< + GfCreateOrUpdateAccessDialogComponent, + CreateOrUpdateAccessDialogParams + >(GfCreateOrUpdateAccessDialogComponent, { data: { access: { alias: access.alias, - id: access.id, grantee: access.grantee === 'Public' ? null : access.grantee, + id: access.id, permissions: access.permissions, type: access.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 f09901e45..3a1616b6f 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -23,6 +23,8 @@ import { EMPTY, Subject, Subscription } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component'; +import { CreateOrUpdateAccountDialogParams } from './create-or-update-account-dialog/interfaces/interfaces'; +import { TransferBalanceDialogParams } from './transfer-balance/interfaces/interfaces'; import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-balance-dialog.component'; @Component({ @@ -179,7 +181,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { name, platformId }: AccountModel) { - const dialogRef = this.dialog.open(GfCreateOrUpdateAccountDialogComponent, { + const dialogRef = this.dialog.open< + GfCreateOrUpdateAccountDialogComponent, + CreateOrUpdateAccountDialogParams + >(GfCreateOrUpdateAccountDialogComponent, { data: { account: { balance, @@ -227,7 +232,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { } private openAccountDetailDialog(aAccountId: string) { - const dialogRef = this.dialog.open(GfAccountDetailDialogComponent, { + const dialogRef = this.dialog.open< + GfAccountDetailDialogComponent, + AccountDetailDialogParams + >(GfAccountDetailDialogComponent, { autoFocus: false, data: { accountId: aAccountId, @@ -237,7 +245,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { !this.hasImpersonationId && hasPermission(this.user?.permissions, permissions.createOrder) && !this.user?.settings?.isRestrictedView - } as AccountDetailDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); @@ -253,12 +261,16 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { } private openCreateAccountDialog() { - const dialogRef = this.dialog.open(GfCreateOrUpdateAccountDialogComponent, { + const dialogRef = this.dialog.open< + GfCreateOrUpdateAccountDialogComponent, + CreateOrUpdateAccountDialogParams + >(GfCreateOrUpdateAccountDialogComponent, { data: { account: { balance: 0, comment: null, currency: this.user?.settings?.baseCurrency, + id: null, isExcluded: false, name: null, platformId: null @@ -295,7 +307,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { } private openTransferBalanceDialog() { - const dialogRef = this.dialog.open(GfTransferBalanceDialogComponent, { + const dialogRef = this.dialog.open< + GfTransferBalanceDialogComponent, + TransferBalanceDialogParams + >(GfTransferBalanceDialogComponent, { data: { accounts: this.accounts }, diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts index ffe4f14f6..a3e6272f8 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts @@ -1,5 +1,5 @@ import { Account } from '@prisma/client'; export interface CreateOrUpdateAccountDialogParams { - account: Account; + account: Omit; } 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 ce99fbf77..6ee02bd8e 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 @@ -28,6 +28,7 @@ import { Subject, Subscription } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { GfCreateOrUpdateActivityDialogComponent } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component'; +import { CreateOrUpdateActivityDialogParams } from './create-or-update-activity-dialog/interfaces/interfaces'; import { GfImportActivitiesDialogComponent } from './import-activities-dialog/import-activities-dialog.component'; import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; @@ -245,11 +246,14 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { } public onImport() { - const dialogRef = this.dialog.open(GfImportActivitiesDialogComponent, { + const dialogRef = this.dialog.open< + GfImportActivitiesDialogComponent, + ImportActivitiesDialogParams + >(GfImportActivitiesDialogComponent, { data: { deviceType: this.deviceType, user: this.user - } as ImportActivitiesDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : undefined, width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); @@ -268,12 +272,15 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { } public onImportDividends() { - const dialogRef = this.dialog.open(GfImportActivitiesDialogComponent, { + const dialogRef = this.dialog.open< + GfImportActivitiesDialogComponent, + ImportActivitiesDialogParams + >(GfImportActivitiesDialogComponent, { data: { activityTypes: ['DIVIDEND'], deviceType: this.deviceType, user: this.user - } as ImportActivitiesDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : undefined, width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); @@ -306,18 +313,18 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { } public openUpdateActivityDialog(aActivity: Activity) { - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfCreateOrUpdateActivityDialogComponent, - { - data: { - activity: aActivity, - accounts: this.user?.accounts, - user: this.user - }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + CreateOrUpdateActivityDialogParams + >(GfCreateOrUpdateActivityDialogComponent, { + data: { + activity: aActivity, + accounts: this.user?.accounts, + user: this.user + }, + height: this.deviceType === 'mobile' ? '98vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() @@ -350,26 +357,26 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { .subscribe((user) => { this.updateUser(user); - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfCreateOrUpdateActivityDialogComponent, - { - data: { - accounts: this.user?.accounts, - activity: { - ...aActivity, - accountId: aActivity?.accountId, - date: new Date(), - id: null, - fee: 0, - type: aActivity?.type ?? 'BUY', - unitPrice: null - }, - user: this.user + CreateOrUpdateActivityDialogParams + >(GfCreateOrUpdateActivityDialogComponent, { + data: { + accounts: this.user?.accounts, + activity: { + ...aActivity, + accountId: aActivity?.accountId, + date: new Date(), + id: null, + fee: 0, + type: aActivity?.type ?? 'BUY', + unitPrice: null }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + user: this.user + }, + height: this.deviceType === 'mobile' ? '98vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() 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 60a39d361..cc454a66a 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts @@ -4,7 +4,6 @@ import { User } from '@ghostfolio/common/interfaces'; import { Account } from '@prisma/client'; export interface CreateOrUpdateActivityDialogParams { - accountId: string; accounts: Account[]; activity: Activity; user: User; diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts index a2131db88..051345e60 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts @@ -3,7 +3,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { Type } from '@prisma/client'; export interface ImportActivitiesDialogParams { - activityTypes: Type[]; + activityTypes?: Type[]; deviceType: string; user: User; } 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 da909a78d..b4de51701 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 @@ -558,7 +558,10 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } private openAccountDetailDialog(aAccountId: string) { - const dialogRef = this.dialog.open(GfAccountDetailDialogComponent, { + const dialogRef = this.dialog.open< + GfAccountDetailDialogComponent, + AccountDetailDialogParams + >(GfAccountDetailDialogComponent, { autoFocus: false, data: { accountId: aAccountId, @@ -568,7 +571,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { !this.hasImpersonationId && hasPermission(this.user?.permissions, permissions.createOrder) && !this.user?.settings?.isRestrictedView - } as AccountDetailDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); 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 eff4e308b..acf3c40eb 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -84,18 +84,18 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit { } public openShowAccessTokenDialog() { - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfUserAccountRegistrationDialogComponent, - { - data: { - deviceType: this.deviceType, - needsToAcceptTermsOfService: this.hasPermissionForSubscription - } as UserAccountRegistrationDialogParams, - disableClose: true, - height: this.deviceType === 'mobile' ? '98vh' : undefined, - width: this.deviceType === 'mobile' ? '100vw' : '30rem' - } - ); + UserAccountRegistrationDialogParams + >(GfUserAccountRegistrationDialogComponent, { + data: { + deviceType: this.deviceType, + needsToAcceptTermsOfService: this.hasPermissionForSubscription + }, + disableClose: true, + height: this.deviceType === 'mobile' ? '98vh' : undefined, + width: this.deviceType === 'mobile' ? '100vw' : '30rem' + }); dialogRef .afterClosed() diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index f52a52975..bd9d7d04c 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -116,18 +116,18 @@ export class UserService extends ObservableStore { permissions.enableSubscriptionInterstitial ) ) { - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfSubscriptionInterstitialDialogComponent, - { - autoFocus: false, - data: { - user - } as SubscriptionInterstitialDialogParams, - disableClose: true, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + SubscriptionInterstitialDialogParams + >(GfSubscriptionInterstitialDialogComponent, { + autoFocus: false, + data: { + user + }, + disableClose: true, + height: this.deviceType === 'mobile' ? '98vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index 3af9bc674..bb66acba8 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -155,14 +155,17 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { dataSource, symbol }: AssetProfileIdentifier) { - const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, { + const dialogRef = this.dialog.open< + GfBenchmarkDetailDialogComponent, + BenchmarkDetailDialogParams + >(GfBenchmarkDetailDialogComponent, { data: { dataSource, symbol, colorScheme: this.user?.settings?.colorScheme, deviceType: this.deviceType, locale: this.locale - } as BenchmarkDetailDialogParams, + }, height: this.deviceType === 'mobile' ? '98vh' : undefined, width: this.deviceType === 'mobile' ? '100vw' : '50rem' }); 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 7fbb1e621..002422c57 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 @@ -199,21 +199,21 @@ export class GfHistoricalMarketDataEditorComponent }) { const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice; - const dialogRef = this.dialog.open( + const dialogRef = this.dialog.open< GfHistoricalMarketDataEditorDialogComponent, - { - data: { - marketPrice, - currency: this.currency, - dataSource: this.dataSource, - dateString: `${yearMonth}-${day}`, - symbol: this.symbol, - user: this.user - } as HistoricalMarketDataEditorDialogParams, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - } - ); + HistoricalMarketDataEditorDialogParams + >(GfHistoricalMarketDataEditorDialogComponent, { + data: { + marketPrice, + currency: this.currency, + dataSource: this.dataSource, + dateString: `${yearMonth}-${day}`, + symbol: this.symbol, + user: this.user + }, + height: this.deviceType === 'mobile' ? '98vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); dialogRef .afterClosed() From 9f36eef39d9cd7c5d5d280713c63620ef5e6819c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 27 Oct 2025 20:46:44 +0100 Subject: [PATCH 038/146] Task/extend Contributing section in README.md (#5864) * Add GitHub Sponsors --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82d5710d1..9f3633f8f 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ Ghostfolio is **100% free** and **open source**. We encourage and support an act Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22%20no%3Aassignee), even for [newcomers](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%20no%3Aassignee). Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://x.com/ghostfolio_) on _X_. We would love to hear from you. -If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio). +If you like to support this project, become a [**Sponsor**](https://github.com/sponsors/ghostfolio), get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio). ## Analytics From cf2dd95906870b7359f39bc3f10750db0fab547d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 27 Oct 2025 20:47:38 +0100 Subject: [PATCH 039/146] Task/add LambdaTest to Sponsors in README.md (#5861) * Add LambdaTest --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 9f3633f8f..1a5cc6e95 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,17 @@ Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/gho If you like to support this project, become a [**Sponsor**](https://github.com/sponsors/ghostfolio), get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio). +## Sponsors + +
+

+ Browser testing via
+ + LambdaTest Logo + +

+
+ ## Analytics ![Alt](https://repobeats.axiom.co/api/embed/281a80b2d0c4af1162866c24c803f1f18e5ed60e.svg 'Repobeats analytics image') From 7dc74fe6812cf1efe85d19f0713e79df06200b8d Mon Sep 17 00:00:00 2001 From: Vansh <140736931+Vansh-Parate@users.noreply.github.com> Date: Tue, 28 Oct 2025 12:40:35 +0530 Subject: [PATCH 040/146] Task/refactor column definitions in AI service (#5834) * Refactor column definitions in AI service * Update changelog --- CHANGELOG.md | 2 + apps/api/src/app/endpoints/ai/ai.service.ts | 79 ++++++++++++++++----- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1024336b3..220b29036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Refactored the generation of the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) +- Refactored the generation of the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) - Improved the language localization for German (`de`) ### Fixed diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts index 4cc4fde65..d07768d69 100644 --- a/apps/api/src/app/endpoints/ai/ai.service.ts +++ b/apps/api/src/app/endpoints/ai/ai.service.ts @@ -14,6 +14,27 @@ import type { ColumnDescriptor } from 'tablemark'; @Injectable() export class AiService { + private static readonly HOLDINGS_TABLE_COLUMN_DEFINITIONS: ({ + key: + | 'ALLOCATION_PERCENTAGE' + | 'ASSET_CLASS' + | 'ASSET_SUB_CLASS' + | 'CURRENCY' + | 'NAME' + | 'SYMBOL'; + } & ColumnDescriptor)[] = [ + { key: 'NAME', name: 'Name' }, + { key: 'SYMBOL', name: 'Symbol' }, + { key: 'CURRENCY', name: 'Currency' }, + { key: 'ASSET_CLASS', name: 'Asset Class' }, + { key: 'ASSET_SUB_CLASS', name: 'Asset Sub Class' }, + { + align: 'right', + key: 'ALLOCATION_PERCENTAGE', + name: 'Allocation in Percentage' + } + ]; + public constructor( private readonly portfolioService: PortfolioService, private readonly propertyService: PropertyService @@ -59,14 +80,10 @@ export class AiService { userId }); - const holdingsTableColumns: ColumnDescriptor[] = [ - { name: 'Name' }, - { name: 'Symbol' }, - { name: 'Currency' }, - { name: 'Asset Class' }, - { name: 'Asset Sub Class' }, - { align: 'right', name: 'Allocation in Percentage' } - ]; + const holdingsTableColumns: ColumnDescriptor[] = + AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.map(({ align, name }) => { + return { name, align: align ?? 'left' }; + }); const holdingsTableRows = Object.values(holdings) .sort((a, b) => { @@ -78,17 +95,45 @@ export class AiService { assetClass, assetSubClass, currency, - name, + name: label, symbol }) => { - return { - Name: name, - Symbol: symbol, - Currency: currency, - 'Asset Class': assetClass ?? '', - 'Asset Sub Class': assetSubClass ?? '', - 'Allocation in Percentage': `${(allocationInPercentage * 100).toFixed(3)}%` - }; + return AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.reduce( + (row, { key, name }) => { + switch (key) { + case 'ALLOCATION_PERCENTAGE': + row[name] = `${(allocationInPercentage * 100).toFixed(3)}%`; + break; + + case 'ASSET_CLASS': + row[name] = assetClass ?? ''; + break; + + case 'ASSET_SUB_CLASS': + row[name] = assetSubClass ?? ''; + break; + + case 'CURRENCY': + row[name] = currency; + break; + + case 'NAME': + row[name] = label; + break; + + case 'SYMBOL': + row[name] = symbol; + break; + + default: + row[name] = ''; + break; + } + + return row; + }, + {} as Record + ); } ); From 8bd47d3f7c6db67d90f997f04ece134c5bf876a9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:30:39 +0100 Subject: [PATCH 041/146] Feature/set up sponsors section on about page (#5862) * Set up sponsors section * Update changelog --- CHANGELOG.md | 1 + .../about/overview/about-overview-page.html | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 220b29036..ad8f2f70e 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 - Added a close holding button to the holding detail dialog +- Added the _Sponsors_ section to the about page - Extended the user detail dialog in the users section of the admin control panel ### Changed diff --git a/apps/client/src/app/pages/about/overview/about-overview-page.html b/apps/client/src/app/pages/about/overview/about-overview-page.html index 4085498a9..72c054170 100644 --- a/apps/client/src/app/pages/about/overview/about-overview-page.html +++ b/apps/client/src/app/pages/about/overview/about-overview-page.html @@ -175,7 +175,7 @@ -
+
}
+ +
+
+

Sponsors

+
+ Browser testing via +
+ + LambdaTest Logo + +
+
+
From 8c80086da168f9eaf5be240852ba3a2656729bdd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:31:16 +0100 Subject: [PATCH 042/146] Bugfix/fix typography hierarchy in resources pages (#5863) * Fix hierarchy --- .../glossary/resources-glossary.component.html | 18 +++++++++--------- .../guides/resources-guides.component.html | 4 ++-- .../markets/resources-markets.component.html | 8 ++++---- .../overview/resources-overview.component.html | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html index b65054bba..fa74cd084 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html @@ -5,7 +5,7 @@
-

Buy and Hold

+

Buy and Hold

Buy and hold is a passive investment strategy where you buy assets and hold them for a long period regardless of fluctuations in the @@ -22,7 +22,7 @@
-

Deflation

+

Deflation

Deflation is a decrease of the general price level for goods and services in an economy over a period of time. @@ -38,7 +38,7 @@
-

Dollar-Cost Averaging (DCA)

+

Dollar-Cost Averaging (DCA)

Dollar-cost averaging is an investment strategy where you split the total amount to be invested across periodic purchases of a @@ -56,7 +56,7 @@
-

Financial Independence

+

Financial Independence

Financial independence is the status of having enough income, for example with a passive income like dividends, to cover your living @@ -73,7 +73,7 @@
-

FIRE

+

FIRE

FIRE is a movement that promotes saving and investing to achieve financial independence and early retirement. @@ -85,7 +85,7 @@
-

Inflation

+

Inflation

Inflation is an increase of the general price level for goods and services in an economy over a period of time. @@ -102,7 +102,7 @@ @if (hasPermissionForSubscription) {
-

Personal Finance Tools

+

Personal Finance Tools

Personal finance tools are software applications that help manage your money, track expenses, set budgets, monitor @@ -118,7 +118,7 @@ }
-

Stagflation

+

Stagflation

Stagflation describes a situation in which there is a stagnant economy with high unemployment and high inflation. @@ -134,7 +134,7 @@
-

Stealth Wealth

+

Stealth Wealth

Stealth wealth is a lifestyle choice where you don’t openly show off your wealth, but instead live quietly to maintain privacy and diff --git a/apps/client/src/app/pages/resources/guides/resources-guides.component.html b/apps/client/src/app/pages/resources/guides/resources-guides.component.html index 3bd8efec6..54b3d1f3e 100644 --- a/apps/client/src/app/pages/resources/guides/resources-guides.component.html +++ b/apps/client/src/app/pages/resources/guides/resources-guides.component.html @@ -5,7 +5,7 @@
-

Boringly Getting Rich

+

Boringly Getting Rich

The Boringly Getting Rich guide supports you to get started with investing. It introduces a strategy utilizing a broadly @@ -21,7 +21,7 @@
-

How do I get my finances in order?

+

How do I get my finances in order?

Before you can think of long-term investing, you have to get your finances in order. Learn how you can reach your financial goals diff --git a/apps/client/src/app/pages/resources/markets/resources-markets.component.html b/apps/client/src/app/pages/resources/markets/resources-markets.component.html index 74d4bb82b..ce780aedf 100644 --- a/apps/client/src/app/pages/resources/markets/resources-markets.component.html +++ b/apps/client/src/app/pages/resources/markets/resources-markets.component.html @@ -3,7 +3,7 @@
-

Crypto Coins Heatmap

+

Crypto Coins Heatmap

With the Crypto Coins Heatmap you can track the daily market movements of cryptocurrencies as a visual snapshot. @@ -17,7 +17,7 @@
-

Fear & Greed Index

+

Fear & Greed Index

The fear and greed index was developed by CNNMoney to measure the primary emotions (fear and greed) that influence how much @@ -32,7 +32,7 @@
-

Inflation Chart

+

Inflation Chart

Inflation Chart helps you find the intrinsic value of stock markets, stock prices, goods and services by adjusting them to the @@ -48,7 +48,7 @@
-

Stock Heatmap

+

Stock Heatmap

With the Stock Heatmap you can track the daily market movements of stocks as a visual snapshot. diff --git a/apps/client/src/app/pages/resources/overview/resources-overview.component.html b/apps/client/src/app/pages/resources/overview/resources-overview.component.html index 39d7c1e62..3a6f18d40 100644 --- a/apps/client/src/app/pages/resources/overview/resources-overview.component.html +++ b/apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -5,7 +5,7 @@
@for (item of overviewItems; track item) {
-

{{ item.title }}

+

{{ item.title }}

{{ item.description }}

Explore {{ item.title }} →
From ed115c59b14cfd0cc69dd9060d91938c0d98e6f1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:30:09 +0100 Subject: [PATCH 043/146] Feature/improve usability of user detail dialog (#5868) * Do not reload on close * Update changelog --- CHANGELOG.md | 1 + .../src/app/components/admin-users/admin-users.component.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad8f2f70e..24103d37d 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 generation of the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental) - Refactored the generation of the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental) +- Improved the usability of the user detail dialog in the users section of the admin control panel - Improved the language localization for German (`de`) ### Fixed 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 fba54b0bb..c0d058ad2 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 @@ -307,7 +307,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { - this.fetchUsers(); this.router.navigate(['.'], { relativeTo: this.route }); }); } From aa8f933110316943b394ff4573461d4740df210a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:33:22 +0100 Subject: [PATCH 044/146] Release 2.212.0 (#5871) --- 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 24103d37d..8245505b5 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.212.0 - 2025-10-29 ### Added diff --git a/package-lock.json b/package-lock.json index 62913d174..50138c7c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.211.0", + "version": "2.212.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.211.0", + "version": "2.212.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 512f61b6d..d7b626f85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.211.0", + "version": "2.212.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 0ea2edd1e55015f086ee081bac53910d2991b03d Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:28:46 -0600 Subject: [PATCH 045/146] Feature/extend menu in activities table component (#5855) * Extend menu in activities table component * Update changelog --- CHANGELOG.md | 6 +++++ .../activities-table.component.html | 14 +++++++----- .../activities-table.component.ts | 22 ++++++++++++------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8245505b5..d0f90277e 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 + +- Extended the activities table menu with a _View Holding_ item + ## 2.212.0 - 2025-10-29 ### Added 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 843832e1a..e230c0bcd 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -437,6 +437,14 @@ class="no-max-width" xPosition="before" > + @if (canClickActivity(element)) { + + }
-
-
-

Sponsors

-
- Browser testing via -
- - LambdaTest Logo - + @if (user?.subscription?.type !== 'Premium') { +
+
+

Sponsors

+
+ Browser testing via +
+ + LambdaTest Logo + +
-
+ }
From f188d1b2ab87b1a3bbe9eb45be21968e95ee32be Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:16:15 +0100 Subject: [PATCH 055/146] Feature/update OSS friends 20251031 (#5879) * Update OSS friends --- apps/client/src/assets/oss-friends.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/client/src/assets/oss-friends.json b/apps/client/src/assets/oss-friends.json index 827b56c3a..2fbf5e27d 100644 --- a/apps/client/src/assets/oss-friends.json +++ b/apps/client/src/assets/oss-friends.json @@ -1,5 +1,5 @@ { - "createdAt": "2025-09-17T00:00:00.000Z", + "createdAt": "2025-10-31T00:00:00.000Z", "data": [ { "name": "Activepieces", @@ -16,6 +16,11 @@ "description": "Argos provides the developer tools to debug tests and detect visual regressions.", "href": "https://argos-ci.com" }, + { + "name": "Bifrost", + "description": "Fastest LLM gateway with adaptive load balancer, cluster mode, guardrails, 1000+ models support & <100 µs overhead at 5k RPS.", + "href": "https://www.getmaxim.ai/bifrost" + }, { "name": "Cal.com", "description": "Cal.com is a scheduling tool that helps you schedule meetings without the back-and-forth emails.", @@ -56,11 +61,6 @@ "description": "Inbox Zero makes it easy to clean up your inbox and reach inbox zero fast. It provides bulk newsletter unsubscribe, cold email blocking, email analytics, and AI automations.", "href": "https://getinboxzero.com" }, - { - "name": "Infisical", - "description": "Open source, end-to-end encrypted platform that lets you securely manage secrets and configs across your team, devices, and infrastructure.", - "href": "https://infisical.com" - }, { "name": "KeepHQ", "description": "Keep is an open-source AIOps (AI for IT operations) platform", From 6177ec0ec57ed60a089efc97739583088efe332e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:18:21 +0100 Subject: [PATCH 056/146] Feature/improve icon of View Holding menu item in activities table (#5881) * Improve icon * Update changelog --- CHANGELOG.md | 1 + .../lib/activities-table/activities-table.component.html | 2 +- .../lib/activities-table/activities-table.component.ts | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2417d3dcc..71370cd35 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 icon of the _View Holding_ menu item in the activities table - Refreshed the cryptocurrencies list ## 2.213.0 - 2025-10-30 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 e230c0bcd..46e1de875 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -440,7 +440,7 @@ @if (canClickActivity(element)) { 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 0b58bda94..1313ef1e2 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -56,8 +56,8 @@ import { documentTextOutline, ellipsisHorizontal, ellipsisVertical, - trashOutline, - walletOutline + tabletLandscapeOutline, + trashOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, Subscription, takeUntil } from 'rxjs'; @@ -154,8 +154,8 @@ export class GfActivitiesTableComponent documentTextOutline, ellipsisHorizontal, ellipsisVertical, - trashOutline, - walletOutline + tabletLandscapeOutline, + trashOutline }); } From b705b8f07b8dc3443300af8af925969f9cabbae6 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 1 Nov 2025 15:07:03 +0700 Subject: [PATCH 057/146] Task/resolve @typescript-eslint/prefer-regexp-exec ESLint rule (#5885) * fix(lint): remove @typescript-eslint/prefer-regexp-exec override * fix(lint): resolve eslint errors --- apps/api/src/services/i18n/i18n.service.ts | 2 +- eslint.config.cjs | 3 +-- libs/common/src/lib/helper.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/api/src/services/i18n/i18n.service.ts b/apps/api/src/services/i18n/i18n.service.ts index 0f1f6239d..cf340d7c6 100644 --- a/apps/api/src/services/i18n/i18n.service.ts +++ b/apps/api/src/services/i18n/i18n.service.ts @@ -65,7 +65,7 @@ export class I18nService { } private parseLanguageCode(aFileName: string) { - const match = aFileName.match(/\.([a-zA-Z]+)\.xlf$/); + const match = /\.([a-zA-Z]+)\.xlf$/.exec(aFileName); return match ? match[1] : DEFAULT_LANGUAGE_CODE; } diff --git a/eslint.config.cjs b/eslint.config.cjs index c7e08821c..7907f4bce 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -189,8 +189,7 @@ module.exports = [ // The following rules are part of @typescript-eslint/stylistic-type-checked // and can be remove once solved - '@typescript-eslint/prefer-nullish-coalescing': 'warn', // TODO: Requires strictNullChecks: true - '@typescript-eslint/prefer-regexp-exec': 'warn' + '@typescript-eslint/prefer-nullish-coalescing': 'warn' // TODO: Requires strictNullChecks: true } })) ]; diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 97b762267..7452b604c 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -375,7 +375,7 @@ export function parseDate(date: string): Date { // Transform 'yyyyMMdd' format to supported format by parse function if (date?.length === 8) { - const match = date.match(/^(\d{4})(\d{2})(\d{2})$/); + const match = /^(\d{4})(\d{2})(\d{2})$/.exec(date); if (match) { const [, year, month, day] = match; From debadd455ea4f14b8e00965721ced1ed5a39973b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 09:44:21 +0100 Subject: [PATCH 058/146] Task/upgrade ng-extract-i18n-merge to version 3.1.0 (#5886) * Upgrade ng-extract-i18n-merge to version 3.1.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 71370cd35..248aaa6ac 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 - Improved the icon of the _View Holding_ menu item in the activities table - Refreshed the cryptocurrencies list +- Upgraded `ng-extract-i18n-merge` from version `3.0.0` to `3.1.0` ## 2.213.0 - 2025-10-30 diff --git a/package-lock.json b/package-lock.json index b306692a3..914a783e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,7 @@ "lodash": "4.17.21", "marked": "15.0.4", "ms": "3.0.0-canary.1", - "ng-extract-i18n-merge": "3.0.0", + "ng-extract-i18n-merge": "3.1.0", "ngx-device-detector": "10.1.0", "ngx-markdown": "20.0.0", "ngx-skeleton-loader": "11.3.0", @@ -32780,9 +32780,9 @@ "license": "MIT" }, "node_modules/ng-extract-i18n-merge": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-3.0.0.tgz", - "integrity": "sha512-vTWtAz6a/wVYxnUMFHp1ur6o4JSLm+LcxdSMV8o8Ml2p5oCsSB4iFd5E6h8Yb8X8D596qyBz0ELgiDmbn4YyRQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-3.1.0.tgz", + "integrity": "sha512-4rJRcpTcP54xf5cjoz3S1By0T04X2RoyQcMDxr4wLdRx3fVxkeP8jeuLzmj9F4G5n0yMQb+6jhUiFERxpkfs1w==", "license": "MIT", "dependencies": { "@angular-devkit/architect": "^0.2000.0", diff --git a/package.json b/package.json index cbbb12652..43970a7f0 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "lodash": "4.17.21", "marked": "15.0.4", "ms": "3.0.0-canary.1", - "ng-extract-i18n-merge": "3.0.0", + "ng-extract-i18n-merge": "3.1.0", "ngx-device-detector": "10.1.0", "ngx-markdown": "20.0.0", "ngx-skeleton-loader": "11.3.0", From 4ecfdadda448d681fb8d6c16deb56d1d566493e5 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sat, 1 Nov 2025 23:59:59 +0700 Subject: [PATCH 059/146] Task/resolve @typescript-eslint/no-unsafe-function-type ESLint rule (#5889) * fix(lint): remove @typescript-eslint/no-unsafe-function-type override * fix(lint): resolve eslint errors --- apps/api/src/app/auth/google.strategy.ts | 3 ++- apps/client/src/app/core/module-preload.service.ts | 2 +- eslint.config.cjs | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/src/app/auth/google.strategy.ts b/apps/api/src/app/auth/google.strategy.ts index 02f82a7a8..3e4b4ca0d 100644 --- a/apps/api/src/app/auth/google.strategy.ts +++ b/apps/api/src/app/auth/google.strategy.ts @@ -3,6 +3,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con import { Injectable, Logger } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Provider } from '@prisma/client'; +import { DoneCallback } from 'passport'; import { Profile, Strategy } from 'passport-google-oauth20'; import { AuthService } from './auth.service'; @@ -29,7 +30,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { _token: string, _refreshToken: string, profile: Profile, - done: Function + done: DoneCallback ) { try { const jwt = await this.authService.validateOAuthLogin({ diff --git a/apps/client/src/app/core/module-preload.service.ts b/apps/client/src/app/core/module-preload.service.ts index fcba48c52..85d9c5e33 100644 --- a/apps/client/src/app/core/module-preload.service.ts +++ b/apps/client/src/app/core/module-preload.service.ts @@ -7,7 +7,7 @@ export class ModulePreloadService implements PreloadingStrategy { /** * Preloads all lazy loading modules with the attribute 'preload' set to true */ - preload(route: Route, load: Function): Observable { + preload(route: Route, load: () => Observable): Observable { return route.data?.preload ? load() : of(null); } } diff --git a/eslint.config.cjs b/eslint.config.cjs index 7907f4bce..cb586e8ed 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -170,7 +170,6 @@ module.exports = [ '@typescript-eslint/no-unsafe-argument': 'warn', '@typescript-eslint/no-unsafe-assignment': 'warn', '@typescript-eslint/no-unsafe-enum-comparison': 'warn', - '@typescript-eslint/no-unsafe-function-type': 'warn', '@typescript-eslint/no-unsafe-member-access': 'warn', '@typescript-eslint/no-unsafe-return': 'warn', '@typescript-eslint/no-unsafe-call': 'warn', From 60bfe1eaa6b0c08ea6235136ffa175c800e3b251 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:01:24 +0700 Subject: [PATCH 060/146] Task/resolve no-constant-binary-expression ESLint rule (#5890) * fix(lint): remove no-constant-binary-expression override * fix(lint): resolve eslint errors --- eslint.config.cjs | 1 - libs/ui/src/lib/fire-calculator/fire-calculator.component.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/eslint.config.cjs b/eslint.config.cjs index cb586e8ed..a88d0cc85 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -152,7 +152,6 @@ module.exports = [ // The following rules are part of eslint:recommended // and can be remove once solved - 'no-constant-binary-expression': 'warn', 'no-loss-of-precision': 'warn', // The following rules are part of @typescript-eslint/recommended-type-checked 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 df7ca79fa..44276ec43 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -185,7 +185,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { 'principalInvestmentAmount' ).value, projectedTotalAmount: - Number(this.getProjectedTotalAmount().toFixed(0)) ?? 0, + Math.round(this.getProjectedTotalAmount()) || 0, retirementDate: this.getRetirementDate() ?? this.DEFAULT_RETIREMENT_DATE }, From e5550a432222c6ce03cdea6562fb177fd031c4bf Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 18:02:37 +0100 Subject: [PATCH 061/146] Task/extend user settings in test files (#5836) * Extend user settings by performance calculation type --- test/import/not-ok/invalid-data-source.json | 8 +++++++- test/import/not-ok/invalid-date-before-min.json | 8 +++++++- test/import/not-ok/invalid-date.json | 8 +++++++- test/import/not-ok/invalid-symbol.json | 8 +++++++- test/import/not-ok/invalid-type.json | 8 +++++++- test/import/not-ok/unavailable-exchange-rate.json | 8 +++++++- test/import/ok/500-activities.json | 3 ++- test/import/ok/btceur.json | 3 ++- test/import/ok/btcusd-short.json | 3 ++- test/import/ok/btcusd.json | 3 ++- test/import/ok/derived-currency.json | 3 ++- test/import/ok/novn-buy-and-sell-partially.json | 3 ++- test/import/ok/novn-buy-and-sell.json | 3 ++- test/import/ok/penthouse-apartment.json | 3 ++- test/import/ok/sample.json | 3 ++- test/import/ok/vti-buy-long-history.json | 3 ++- test/import/ok/without-accounts.json | 3 ++- 17 files changed, 64 insertions(+), 17 deletions(-) diff --git a/test/import/not-ok/invalid-data-source.json b/test/import/not-ok/invalid-data-source.json index 472e295ee..f8e920c67 100644 --- a/test/import/not-ok/invalid-data-source.json +++ b/test/import/not-ok/invalid-data-source.json @@ -14,5 +14,11 @@ "type": "BUY", "unitPrice": 100.0 } - ] + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } } diff --git a/test/import/not-ok/invalid-date-before-min.json b/test/import/not-ok/invalid-date-before-min.json index 3040581b2..260d79166 100644 --- a/test/import/not-ok/invalid-date-before-min.json +++ b/test/import/not-ok/invalid-date-before-min.json @@ -14,5 +14,11 @@ "type": "BUY", "unitPrice": 100.0 } - ] + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } } diff --git a/test/import/not-ok/invalid-date.json b/test/import/not-ok/invalid-date.json index 99cd6d156..4522c6dcc 100644 --- a/test/import/not-ok/invalid-date.json +++ b/test/import/not-ok/invalid-date.json @@ -14,5 +14,11 @@ "type": "BUY", "unitPrice": 100.0 } - ] + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } } diff --git a/test/import/not-ok/invalid-symbol.json b/test/import/not-ok/invalid-symbol.json index 14f0051ec..0bbbf53db 100644 --- a/test/import/not-ok/invalid-symbol.json +++ b/test/import/not-ok/invalid-symbol.json @@ -14,5 +14,11 @@ "type": "BUY", "unitPrice": 100.0 } - ] + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } } diff --git a/test/import/not-ok/invalid-type.json b/test/import/not-ok/invalid-type.json index a23f72411..a8967d81a 100644 --- a/test/import/not-ok/invalid-type.json +++ b/test/import/not-ok/invalid-type.json @@ -14,5 +14,11 @@ "type": "", "unitPrice": 100.0 } - ] + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } } diff --git a/test/import/not-ok/unavailable-exchange-rate.json b/test/import/not-ok/unavailable-exchange-rate.json index 4d8be156a..66c7044d7 100644 --- a/test/import/not-ok/unavailable-exchange-rate.json +++ b/test/import/not-ok/unavailable-exchange-rate.json @@ -15,5 +15,11 @@ "date": "1990-01-01T00:00:00.000Z", "symbol": "MSFT" } - ] + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } } diff --git a/test/import/ok/500-activities.json b/test/import/ok/500-activities.json index b691a6f9f..2793c695e 100644 --- a/test/import/ok/500-activities.json +++ b/test/import/ok/500-activities.json @@ -6019,7 +6019,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/btceur.json b/test/import/ok/btceur.json index b370682f9..ae9eb8921 100644 --- a/test/import/ok/btceur.json +++ b/test/import/ok/btceur.json @@ -23,7 +23,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/btcusd-short.json b/test/import/ok/btcusd-short.json index bc4152de9..6f25a7740 100644 --- a/test/import/ok/btcusd-short.json +++ b/test/import/ok/btcusd-short.json @@ -36,7 +36,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/btcusd.json b/test/import/ok/btcusd.json index fc2e1f66e..4a85f967e 100644 --- a/test/import/ok/btcusd.json +++ b/test/import/ok/btcusd.json @@ -23,7 +23,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/derived-currency.json b/test/import/ok/derived-currency.json index e740c1ae3..637ab21b6 100644 --- a/test/import/ok/derived-currency.json +++ b/test/import/ok/derived-currency.json @@ -31,7 +31,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/novn-buy-and-sell-partially.json b/test/import/ok/novn-buy-and-sell-partially.json index 8c5778566..3bdd7eb7e 100644 --- a/test/import/ok/novn-buy-and-sell-partially.json +++ b/test/import/ok/novn-buy-and-sell-partially.json @@ -27,7 +27,8 @@ ], "user": { "settings": { - "currency": "CHF" + "currency": "CHF", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/novn-buy-and-sell.json b/test/import/ok/novn-buy-and-sell.json index 71ee9b7a9..6ae519d87 100644 --- a/test/import/ok/novn-buy-and-sell.json +++ b/test/import/ok/novn-buy-and-sell.json @@ -27,7 +27,8 @@ ], "user": { "settings": { - "currency": "CHF" + "currency": "CHF", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/penthouse-apartment.json b/test/import/ok/penthouse-apartment.json index 2bc7f0cf8..0c35521e6 100644 --- a/test/import/ok/penthouse-apartment.json +++ b/test/import/ok/penthouse-apartment.json @@ -47,7 +47,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/sample.json b/test/import/ok/sample.json index 21277129f..bc2798718 100644 --- a/test/import/ok/sample.json +++ b/test/import/ok/sample.json @@ -147,7 +147,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/vti-buy-long-history.json b/test/import/ok/vti-buy-long-history.json index c8cd25e60..88b38d2b1 100644 --- a/test/import/ok/vti-buy-long-history.json +++ b/test/import/ok/vti-buy-long-history.json @@ -40,7 +40,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } diff --git a/test/import/ok/without-accounts.json b/test/import/ok/without-accounts.json index 8a24f86fc..2283dd889 100644 --- a/test/import/ok/without-accounts.json +++ b/test/import/ok/without-accounts.json @@ -47,7 +47,8 @@ ], "user": { "settings": { - "currency": "USD" + "currency": "USD", + "performanceCalculationType": "ROAI" } } } From 5598b3780c008d20c19afba5869718be58a28bf7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 18:37:43 +0100 Subject: [PATCH 062/146] Feature/set up unit test for BTCEUR in base currency EUR (#5778) * Set up test --- ...ulator-btceur-in-base-currency-eur.spec.ts | 140 ++++++++++++++++++ .../exchange-rate-data.service.mock.ts | 10 ++ 2 files changed, 150 insertions(+) create mode 100644 apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts 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 new file mode 100644 index 000000000..87893e647 --- /dev/null +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts @@ -0,0 +1,140 @@ +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { + activityDummyData, + loadExportFile, + symbolProfileDummyData, + userDummyData +} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { 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 { ExportResponse } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { Big } from 'big.js'; +import { join } from 'node:path'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/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 exportResponse: ExportResponse; + + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let exchangeRateDataService: ExchangeRateDataService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeAll(() => { + exportResponse = loadExportFile( + join(__dirname, '../../../../../../../test/import/ok/btceur.json') + ); + }); + + beforeEach(() => { + configurationService = new ConfigurationService(); + + currentRateService = new CurrentRateService(null, null, null, null); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null); + + redisCacheService = new RedisCacheService(null, null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('get current positions', () => { + it.only('with BTCUSD buy (in EUR)', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime()); + + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: 4.46, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: activity.dataSource, + name: 'Bitcoin', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: 44558.42 + }) + ); + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: 'EUR', + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + expect(portfolioSnapshot.positions[0].fee).toEqual(new Big(4.46)); + expect( + portfolioSnapshot.positions[0].feeInBaseCurrency.toNumber() + ).toBeCloseTo(3.94, 1); + }); + }); +}); 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 8f5d1c28a..076375523 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 @@ -17,11 +17,21 @@ export const ExchangeRateDataServiceMock = { '2023-07-10': 0.8854 } }); + } else if (targetCurrency === 'EUR') { + return Promise.resolve({ + EUREUR: { + '2021-12-12': 1 + }, + USDEUR: { + '2021-12-12': 0.8855 + } + }); } else if (targetCurrency === 'USD') { return Promise.resolve({ USDUSD: { '2018-01-01': 1, '2021-11-16': 1, + '2021-12-12': 1, '2023-07-10': 1 } }); From 96cad6ad7a54f0cbafaf83732daf7e6be080c66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20G=C3=BCnther?= Date: Sat, 1 Nov 2025 18:39:38 +0100 Subject: [PATCH 063/146] Feature/atomic data replacement during historical market data gathering (#5858) * Atomic data replacement during historical market data gathering * Update changelog --- CHANGELOG.md | 1 + .../api/src/services/interfaces/interfaces.ts | 1 + .../market-data/market-data.service.ts | 55 +++++++++++++++++++ .../data-gathering.processor.ts | 14 ++++- .../data-gathering/data-gathering.service.ts | 8 +-- 5 files changed, 72 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 248aaa6ac..15e89924f 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 - Improved the icon of the _View Holding_ menu item in the activities table +- Ensured atomic data replacement during historical market data gathering - Refreshed the cryptocurrencies list - Upgraded `ng-extract-i18n-merge` from version `3.0.0` to `3.1.0` diff --git a/apps/api/src/services/interfaces/interfaces.ts b/apps/api/src/services/interfaces/interfaces.ts index 7469754b5..492c2bd35 100644 --- a/apps/api/src/services/interfaces/interfaces.ts +++ b/apps/api/src/services/interfaces/interfaces.ts @@ -20,4 +20,5 @@ export interface DataProviderResponse { export interface DataGatheringItem extends AssetProfileIdentifier { date?: Date; + force?: boolean; } 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 38ad61663..d318b9a70 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -132,6 +132,61 @@ export class MarketDataService { }); } + /** + * Atomically replace market data for a symbol within a date range. + * Deletes existing data in the range and inserts new data within a single + * transaction to prevent data loss if the operation fails. + */ + public async replaceForSymbol({ + data, + dataSource, + symbol + }: AssetProfileIdentifier & { data: Prisma.MarketDataUpdateInput[] }) { + await this.prismaService.$transaction(async (prisma) => { + if (data.length > 0) { + let minTime = Infinity; + let maxTime = -Infinity; + + for (const { date } of data) { + const time = (date as Date).getTime(); + + if (time < minTime) { + minTime = time; + } + + if (time > maxTime) { + maxTime = time; + } + } + + const minDate = new Date(minTime); + const maxDate = new Date(maxTime); + + await prisma.marketData.deleteMany({ + where: { + dataSource, + symbol, + date: { + gte: minDate, + lte: maxDate + } + } + }); + + await prisma.marketData.createMany({ + data: data.map(({ date, marketPrice, state }) => ({ + dataSource, + symbol, + date: date as Date, + marketPrice: marketPrice as number, + state: state as MarketDataState + })), + skipDuplicates: true + }); + } + }); + } + public async updateAssetProfileIdentifier( oldAssetProfileIdentifier: AssetProfileIdentifier, newAssetProfileIdentifier: AssetProfileIdentifier diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts index 1a172f3ea..1a4038652 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts @@ -100,7 +100,7 @@ export class DataGatheringProcessor { name: GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME }) public async gatherHistoricalMarketData(job: Job) { - const { dataSource, date, symbol } = job.data; + const { dataSource, date, force, symbol } = job.data; try { let currentDate = parseISO(date as unknown as string); @@ -109,7 +109,7 @@ export class DataGatheringProcessor { `Historical market data gathering has been started for ${symbol} (${dataSource}) at ${format( currentDate, DATE_FORMAT - )}`, + )}${force ? ' (forced update)' : ''}`, `DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME})` ); @@ -157,7 +157,15 @@ export class DataGatheringProcessor { currentDate = addDays(currentDate, 1); } - await this.marketDataService.updateMany({ data }); + if (force) { + await this.marketDataService.replaceForSymbol({ + data, + dataSource, + symbol + }); + } else { + await this.marketDataService.updateMany({ data }); + } Logger.log( `Historical market data gathering has been completed for ${symbol} (${dataSource}) at ${format( 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 2d3ec45ad..c433f692f 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 @@ -2,7 +2,6 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { DataGatheringItem } 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'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; @@ -41,7 +40,6 @@ export class DataGatheringService { private readonly dataGatheringQueue: Queue, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, - private readonly marketDataService: MarketDataService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly symbolProfileService: SymbolProfileService @@ -95,8 +93,6 @@ export class DataGatheringService { } public async gatherSymbol({ dataSource, date, symbol }: DataGatheringItem) { - await this.marketDataService.deleteMany({ dataSource, symbol }); - const dataGatheringItems = (await this.getSymbolsMax()) .filter((dataGatheringItem) => { return ( @@ -111,6 +107,7 @@ export class DataGatheringService { await this.gatherSymbols({ dataGatheringItems, + force: true, priority: DATA_GATHERING_QUEUE_PRIORITY_HIGH }); } @@ -274,9 +271,11 @@ export class DataGatheringService { public async gatherSymbols({ dataGatheringItems, + force = false, priority }: { dataGatheringItems: DataGatheringItem[]; + force?: boolean; priority: number; }) { await this.addJobsToQueue( @@ -285,6 +284,7 @@ export class DataGatheringService { data: { dataSource, date, + force, symbol }, name: GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME, From a4040c3c3c91eb01fa66bb53b9a95663f78f8ecc Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:28:22 +0100 Subject: [PATCH 064/146] Task/remove Internet Identity as social login provider (#5891) * Remove Internet Identity * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/auth/auth.controller.ts | 17 -- apps/api/src/app/auth/auth.service.ts | 37 --- ...ogin-with-access-token-dialog.component.ts | 18 +- .../login-with-access-token-dialog.html | 11 - .../pages/register/register-page.component.ts | 12 - .../src/app/pages/register/register-page.html | 14 -- .../app/services/internet-identity.service.ts | 56 ----- .../src/assets/icons/internet-computer.svg | 28 --- package-lock.json | 212 ++---------------- package.json | 5 - 11 files changed, 18 insertions(+), 393 deletions(-) delete mode 100644 apps/client/src/app/services/internet-identity.service.ts delete mode 100644 apps/client/src/assets/icons/internet-computer.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index 15e89924f..2e4b9af16 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 - Improved the icon of the _View Holding_ menu item in the activities table - Ensured atomic data replacement during historical market data gathering +- Removed _Internet Identity_ as a social login provider - Refreshed the cryptocurrencies list - Upgraded `ng-extract-i18n-merge` from version `3.0.0` to `3.1.0` diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index 13d8e37f6..57fd04bc7 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -102,23 +102,6 @@ export class AuthController { } } - @Post('internet-identity') - public async internetIdentityLogin( - @Body() body: { principalId: string } - ): Promise { - try { - const authToken = await this.authService.validateInternetIdentityLogin( - body.principalId - ); - return { authToken }; - } catch { - throw new HttpException( - getReasonPhrase(StatusCodes.FORBIDDEN), - StatusCodes.FORBIDDEN - ); - } - } - @Get('webauthn/generate-registration-options') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async generateRegistrationOptions() { diff --git a/apps/api/src/app/auth/auth.service.ts b/apps/api/src/app/auth/auth.service.ts index ceff492a0..a6ee5d260 100644 --- a/apps/api/src/app/auth/auth.service.ts +++ b/apps/api/src/app/auth/auth.service.ts @@ -4,7 +4,6 @@ import { PropertyService } from '@ghostfolio/api/services/property/property.serv import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import { Provider } from '@prisma/client'; import { ValidateOAuthLoginParams } from './interfaces/interfaces'; @@ -44,42 +43,6 @@ export class AuthService { }); } - public async validateInternetIdentityLogin(principalId: string) { - try { - const provider: Provider = 'INTERNET_IDENTITY'; - - let [user] = await this.userService.users({ - where: { provider, thirdPartyId: principalId } - }); - - if (!user) { - const isUserSignupEnabled = - await this.propertyService.isUserSignupEnabled(); - - if (!isUserSignupEnabled || true) { - throw new Error('Sign up forbidden'); - } - - // Create new user if not found - user = await this.userService.createUser({ - data: { - provider, - thirdPartyId: principalId - } - }); - } - - return this.jwtService.sign({ - id: user.id - }); - } catch (error) { - throw new InternalServerErrorException( - 'validateInternetIdentityLogin', - error.message - ); - } - } - public async validateOAuthLogin({ provider, thirdPartyId 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 aaca03594..c0926150f 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,10 +1,8 @@ import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; -import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service'; import { KEY_STAY_SIGNED_IN, SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service'; -import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; @@ -21,7 +19,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Router } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { eyeOffOutline, eyeOutline } from 'ionicons/icons'; @@ -55,10 +52,7 @@ export class GfLoginWithAccessTokenDialogComponent { public constructor( @Inject(MAT_DIALOG_DATA) public data: LoginWithAccessTokenDialogParams, public dialogRef: MatDialogRef, - private internetIdentityService: InternetIdentityService, - private router: Router, - private settingsStorageService: SettingsStorageService, - private tokenStorageService: TokenStorageService + private settingsStorageService: SettingsStorageService ) { addIcons({ eyeOffOutline, eyeOutline }); } @@ -81,14 +75,4 @@ export class GfLoginWithAccessTokenDialogComponent { }); } } - - public async onLoginWithInternetIdentity() { - try { - const { authToken } = await this.internetIdentityService.login(); - - this.tokenStorageService.saveToken(authToken); - this.dialogRef.close(); - this.router.navigate(['/']); - } catch {} - } } 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 b51802caf..15e68822a 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 @@ -26,17 +26,6 @@ @if (data.hasPermissionToUseSocialLogin) {
or
- @if (hasPermissionForSocialLogin) {
or
- @if (false) { - - }
(); - - public constructor(private http: HttpClient) {} - - public async login(): Promise { - const authClient = await AuthClient.create({ - idleOptions: { - disableDefaultIdleCallback: true, - disableIdle: true - } - }); - - return new Promise((resolve, reject) => { - authClient.login({ - onError: async () => { - return reject(); - }, - onSuccess: () => { - const principalId = authClient.getIdentity().getPrincipal(); - - this.http - .post(`/api/v1/auth/internet-identity`, { - principalId: principalId.toText() - }) - .pipe( - catchError(() => { - reject(); - return EMPTY; - }), - takeUntil(this.unsubscribeSubject) - ) - .subscribe((response) => { - resolve(response); - }); - } - }); - }); - } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} diff --git a/apps/client/src/assets/icons/internet-computer.svg b/apps/client/src/assets/icons/internet-computer.svg deleted file mode 100644 index 6a1bf6c86..000000000 --- a/apps/client/src/assets/icons/internet-computer.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/package-lock.json b/package-lock.json index 914a783e0..3d2dfa25b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,11 +23,6 @@ "@angular/service-worker": "20.2.4", "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.0", - "@dfinity/agent": "0.15.7", - "@dfinity/auth-client": "0.15.7", - "@dfinity/candid": "0.15.7", - "@dfinity/identity": "0.15.7", - "@dfinity/principal": "0.15.7", "@internationalized/number": "3.6.3", "@ionic/angular": "8.7.3", "@keyv/redis": "4.4.0", @@ -4713,6 +4708,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -4725,6 +4721,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -4953,73 +4950,6 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@dfinity/agent": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.15.7.tgz", - "integrity": "sha512-w34yvlUTpPBG8nLOD0t/ao3k2xonOFq4QGvfJ1HiS/nIggdza/3xC3nLBszGrjVYWj1jqu8BLFvQXCAeWin75A==", - "license": "Apache-2.0", - "dependencies": { - "base64-arraybuffer": "^0.2.0", - "bignumber.js": "^9.0.0", - "borc": "^2.1.1", - "js-sha256": "0.9.0", - "simple-cbor": "^0.4.1", - "ts-node": "^10.8.2" - }, - "peerDependencies": { - "@dfinity/candid": "^0.15.7", - "@dfinity/principal": "^0.15.7" - } - }, - "node_modules/@dfinity/auth-client": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@dfinity/auth-client/-/auth-client-0.15.7.tgz", - "integrity": "sha512-f6cRqXayCf+7+9gNcDnAZZwJrgBYKIzfxjxeRLlpsueQeo+E/BX2yVSANxzTkCNc4U3p+ttHI1RNtasLunYTcA==", - "license": "Apache-2.0", - "dependencies": { - "idb": "^7.0.2" - }, - "peerDependencies": { - "@dfinity/agent": "^0.15.7", - "@dfinity/identity": "^0.15.7", - "@dfinity/principal": "^0.15.7" - } - }, - "node_modules/@dfinity/candid": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.15.7.tgz", - "integrity": "sha512-lTcjK/xrSyT7wvUQ2pApG+yklQAwxaofQ04D1IWv0/8gKbY0eUbh8G2w6+CypJ15Hb1CH24ijUj8nWdeX/z3jg==", - "license": "Apache-2.0", - "dependencies": { - "ts-node": "^10.8.2" - } - }, - "node_modules/@dfinity/identity": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@dfinity/identity/-/identity-0.15.7.tgz", - "integrity": "sha512-kBAkx9wq78jSQf6T5aayLyWm8YgtOZw8bW6+OuzX6tR3hkAEa85A9TcKA7BjkmMWSIskjEDVQub4fFfKWS2vOQ==", - "license": "Apache-2.0", - "dependencies": { - "borc": "^2.1.1", - "js-sha256": "^0.9.0", - "tweetnacl": "^1.0.1" - }, - "peerDependencies": { - "@dfinity/agent": "^0.15.7", - "@dfinity/principal": "^0.15.7", - "@peculiar/webcrypto": "^1.4.0" - } - }, - "node_modules/@dfinity/principal": { - "version": "0.15.7", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.15.7.tgz", - "integrity": "sha512-6/AkYzpGEH6Jw/0+B/EeeQn+5u2GDDvRLt1kQPhIG4txQYFnOy04H3VvyrymmfAj6/CXUgrOrux6OxgYSLYVJg==", - "license": "Apache-2.0", - "dependencies": { - "js-sha256": "^0.9.0", - "ts-node": "^10.8.2" - } - }, "node_modules/@discoveryjs/json-ext": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", @@ -11908,36 +11838,6 @@ "tslib": "^2.8.1" } }, - "node_modules/@peculiar/json-schema": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", - "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", - "license": "MIT", - "peer": true, - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@peculiar/webcrypto": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz", - "integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/json-schema": "^1.1.12", - "pvtsutils": "^1.3.5", - "tslib": "^2.6.2", - "webcrypto-core": "^1.8.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/@phenomnomnominal/tsquery": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz", @@ -13754,24 +13654,28 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, "license": "MIT" }, "node_modules/@tufjs/canonical-json": { @@ -15686,6 +15590,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -15732,6 +15637,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -16057,6 +15963,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -16709,14 +16616,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -17040,30 +16939,6 @@ "popper.js": "^1.16.1" } }, - "node_modules/borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/borc/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -17160,6 +17035,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, "funding": [ { "type": "github", @@ -19325,6 +19201,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, "license": "MIT" }, "node_modules/cron": { @@ -20808,12 +20685,6 @@ "dev": true, "license": "MIT" }, - "node_modules/delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ==", - "license": "BSD-2-Clause" - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -20908,6 +20779,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -24840,12 +24712,6 @@ "postcss": "^8.1.0" } }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, "node_modules/identity-obj-proxy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", @@ -25837,15 +25703,6 @@ "dev": true, "license": "ISC" }, - "node_modules/iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -30098,12 +29955,6 @@ "license": "MIT", "peer": true }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -30316,15 +30167,6 @@ "dev": true, "license": "ISC" }, - "node_modules/json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha512-L3mEegEWHRekSHjc7+sc8eJhba9Clq1PZ8kMkzf8OxElhXc8O4TS5MwcVlj9aEbm5dr81N90WHC5nAz3UO971w==", - "license": "MIT", - "dependencies": { - "delimit-stream": "0.1.0" - } - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -32083,6 +31925,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, "license": "ISC" }, "node_modules/make-fetch-happen": { @@ -38081,12 +37924,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/simple-cbor": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "license": "ISC" - }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -40042,6 +39879,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -40219,12 +40057,6 @@ "node": "*" } }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" - }, "node_modules/twitter-api-v2": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.23.0.tgz", @@ -40795,6 +40627,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -41110,20 +40943,6 @@ "license": "MIT", "optional": true }, - "node_modules/webcrypto-core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz", - "integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@peculiar/asn1-schema": "^2.3.13", - "@peculiar/json-schema": "^1.1.12", - "asn1js": "^3.0.5", - "pvtsutils": "^1.3.5", - "tslib": "^2.7.0" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -42424,6 +42243,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/package.json b/package.json index 43970a7f0..48ebb9215 100644 --- a/package.json +++ b/package.json @@ -69,11 +69,6 @@ "@angular/service-worker": "20.2.4", "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.0", - "@dfinity/agent": "0.15.7", - "@dfinity/auth-client": "0.15.7", - "@dfinity/candid": "0.15.7", - "@dfinity/identity": "0.15.7", - "@dfinity/principal": "0.15.7", "@internationalized/number": "3.6.3", "@ionic/angular": "8.7.3", "@keyv/redis": "4.4.0", From c8e6f9b38117b8256a1589a9e7ff71d294e8a66e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:28:51 +0100 Subject: [PATCH 065/146] Task/upgrade countries-list to version 3.2.0 (#5888) * Upgrade countries-list to version 3.2.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 2e4b9af16..f0a46c801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ensured atomic data replacement during historical market data gathering - Removed _Internet Identity_ as a social login provider - Refreshed the cryptocurrencies list +- Upgraded `countries-list` from version `3.1.1` to `3.2.0` - Upgraded `ng-extract-i18n-merge` from version `3.0.0` to `3.1.0` ## 2.213.0 - 2025-10-30 diff --git a/package-lock.json b/package-lock.json index 3d2dfa25b..0812c99b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "class-validator": "0.14.2", "color": "5.0.0", "countries-and-timezones": "3.8.0", - "countries-list": "3.1.1", + "countries-list": "3.2.0", "countup.js": "2.9.0", "date-fns": "4.1.0", "dotenv": "17.2.3", @@ -18523,9 +18523,9 @@ } }, "node_modules/countries-list": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.1.1.tgz", - "integrity": "sha512-nPklKJ5qtmY5MdBKw1NiBAoyx5Sa7p2yPpljZyQ7gyCN1m+eMFs9I6CT37Mxt8zvR5L3VzD3DJBE4WQzX3WF4A==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.2.0.tgz", + "integrity": "sha512-HYHAo2fwEsG3TmbsNdVmIQPHizRlqeYMTtLEAl0IANG/3jRYX7p3NR6VapDqKP0n60TmsRy1dyRjVN5JbywDbA==", "license": "MIT" }, "node_modules/countup.js": { diff --git a/package.json b/package.json index 48ebb9215..4f52177d2 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "class-validator": "0.14.2", "color": "5.0.0", "countries-and-timezones": "3.8.0", - "countries-list": "3.1.1", + "countries-list": "3.2.0", "countup.js": "2.9.0", "date-fns": "4.1.0", "dotenv": "17.2.3", From 6fd0a9a5c258807ee744a473bdab22d10d081da1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:38:41 +0100 Subject: [PATCH 066/146] Task/upgrade twitter-api-v2 to version 1.27.0 (#5892) * Upgrade twitter-api-v2 to version 1.27.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 f0a46c801..cf13e8ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refreshed the cryptocurrencies list - Upgraded `countries-list` from version `3.1.1` to `3.2.0` - Upgraded `ng-extract-i18n-merge` from version `3.0.0` to `3.1.0` +- Upgraded `twitter-api-v2` from version `1.23.0` to `1.27.0` ## 2.213.0 - 2025-10-30 diff --git a/package-lock.json b/package-lock.json index 0812c99b9..286dc628c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,7 +88,7 @@ "stripe": "18.5.0", "svgmap": "2.12.2", "tablemark": "4.1.0", - "twitter-api-v2": "1.23.0", + "twitter-api-v2": "1.27.0", "uuid": "11.1.0", "yahoo-finance2": "3.10.0", "zone.js": "0.15.1" @@ -40058,9 +40058,9 @@ } }, "node_modules/twitter-api-v2": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.23.0.tgz", - "integrity": "sha512-5i1agETVpTuY68Zuk9i2B3N9wHzc4JIWw0WKyG4CEaFv9mRKmU87roa+U1oYYXTChWb0HMcqfkwoBJHYmLbeDA==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.27.0.tgz", + "integrity": "sha512-hbIFwzg0NeOcFOdmJqtKMCXjLjc0INff/7NwhnZ2zpnw65oku8i+0eMxo5M0iTc1hs+inD/IpDw3S0Xh2c45QQ==", "license": "Apache-2.0" }, "node_modules/type-check": { diff --git a/package.json b/package.json index 4f52177d2..146b27c52 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "stripe": "18.5.0", "svgmap": "2.12.2", "tablemark": "4.1.0", - "twitter-api-v2": "1.23.0", + "twitter-api-v2": "1.27.0", "uuid": "11.1.0", "yahoo-finance2": "3.10.0", "zone.js": "0.15.1" From af2c46830627c8e6842f1b6ae1286c30270eaf31 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 1 Nov 2025 20:40:06 +0100 Subject: [PATCH 067/146] Release 2.214.0 (#5893) --- 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 cf13e8ec3..fa39b0061 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.214.0 - 2025-11-01 ### Changed diff --git a/package-lock.json b/package-lock.json index 286dc628c..6429912bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.213.0", + "version": "2.214.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.213.0", + "version": "2.214.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 146b27c52..7648cee02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.213.0", + "version": "2.214.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From a92f94e6970d84137cc093e7756c7456b39c2b79 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 2 Nov 2025 02:58:46 +0700 Subject: [PATCH 068/146] Feature/migrate client build executor to @nx/angular:browser-esbuild (#5883) * Migrate client build executor to @nx/angular:browser-esbuild * Update changelog --- CHANGELOG.md | 6 ++ apps/client/project.json | 56 +++++++++---------- .../portfolio-proportion-chart.component.ts | 3 +- .../treemap-chart/treemap-chart.component.ts | 3 +- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa39b0061..8ba1a6d7e 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 + +- Changed the build executor of the client from `@nx/angular:webpack-browser` to `@nx/angular:browser-esbuild` + ## 2.214.0 - 2025-11-01 ### Changed diff --git a/apps/client/project.json b/apps/client/project.json index adb63d5c1..0d3571cdf 100644 --- a/apps/client/project.json +++ b/apps/client/project.json @@ -61,30 +61,30 @@ }, "targets": { "build": { - "executor": "@nx/angular:webpack-browser", + "executor": "@nx/angular:browser-esbuild", "options": { - "deleteOutputPath": false, - "localize": true, - "outputPath": "dist/apps/client", "index": "apps/client/src/index.html", "main": "apps/client/src/main.ts", - "polyfills": "apps/client/src/polyfills.ts", + "outputPath": "dist/apps/client", "tsConfig": "apps/client/tsconfig.app.json", + "buildOptimizer": false, + "deleteOutputPath": false, + "extractLicenses": false, + "localize": true, + "namedChunks": true, + "ngswConfigPath": "apps/client/ngsw-config.json", + "optimization": false, + "polyfills": "apps/client/src/polyfills.ts", + "scripts": ["node_modules/marked/marked.min.js"], + "serviceWorker": true, + "sourceMap": true, "styles": [ "apps/client/src/assets/fonts/inter.css", "apps/client/src/styles/theme.scss", "apps/client/src/styles.scss", "node_modules/open-color/open-color.css" ], - "scripts": ["node_modules/marked/marked.min.js"], - "vendorChunk": true, - "extractLicenses": false, - "buildOptimizer": false, - "sourceMap": true, - "optimization": false, - "namedChunks": true, - "serviceWorker": true, - "ngswConfigPath": "apps/client/ngsw-config.json" + "vendorChunk": true }, "configurations": { "development-ca": { @@ -136,19 +136,6 @@ "localize": ["zh"] }, "production": { - "fileReplacements": [ - { - "replace": "apps/client/src/environments/environment.ts", - "with": "apps/client/src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, "budgets": [ { "type": "initial", @@ -160,7 +147,20 @@ "maximumWarning": "6kb", "maximumError": "10kb" } - ] + ], + "buildOptimizer": true, + "extractLicenses": true, + "fileReplacements": [ + { + "replace": "apps/client/src/environments/environment.ts", + "with": "apps/client/src/environments/environment.prod.ts" + } + ], + "namedChunks": false, + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "vendorChunk": false } }, "outputs": ["{options.outputPath}"], 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 3f062a374..2d8a03ac0 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 @@ -31,6 +31,7 @@ import ChartDataLabels from 'chartjs-plugin-datalabels'; import { isUUID } from 'class-validator'; import Color from 'color'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import OpenColor from 'open-color'; import { translate } from '../i18n'; @@ -47,7 +48,7 @@ const { teal, violet, yellow -} = require('open-color'); +} = OpenColor; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index 4e06d49cc..6ae958b83 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -33,10 +33,11 @@ import { isUUID } from 'class-validator'; import { differenceInDays, max } from 'date-fns'; import { orderBy } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import OpenColor from 'open-color'; import { GetColorParams } from './interfaces/interfaces'; -const { gray, green, red } = require('open-color'); +const { gray, green, red } = OpenColor; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, From 592baec9f3222502116fbdb1ef417546d2acc257 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:31:16 +0100 Subject: [PATCH 069/146] Bugfix/fix style of safe withdrawal rate selector (#5899) * Fix style of selector * Update changelog --- CHANGELOG.md | 4 ++ .../app/pages/portfolio/fire/fire-page.scss | 14 +++++ apps/client/src/styles/theme.scss | 61 +++++++++---------- apps/client/src/styles/variables.scss | 4 ++ 4 files changed, 51 insertions(+), 32 deletions(-) create mode 100644 apps/client/src/styles/variables.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ba1a6d7e..5ecf2a57d 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 - Changed the build executor of the client from `@nx/angular:webpack-browser` to `@nx/angular:browser-esbuild` +### Fixed + +- Fixed the style of the safe withdrawal rate selector in the _FIRE_ section (experimental) + ## 2.214.0 - 2025-11-01 ### Changed diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.scss b/apps/client/src/app/pages/portfolio/fire/fire-page.scss index 2892885c9..3a0618ed6 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.scss +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.scss @@ -1,9 +1,21 @@ +@use '../../../../styles/variables.scss' as variables; + +@mixin select-arrow($color) { + background-image: url("data:image/svg+xml;utf8,"); +} + :host { display: block; .safe-withdrawal-rate-select { + @include select-arrow(variables.$dark-primary-text); + + appearance: none; background-color: transparent; + background-position: right 0 center; + background-repeat: no-repeat; color: rgb(var(--dark-primary-text)); + padding: 0 0.75rem 0 0.25rem; &:focus { box-shadow: none; @@ -14,6 +26,8 @@ :host-context(.theme-dark) { .safe-withdrawal-rate-select { + @include select-arrow(variables.$light-primary-text); + color: rgb(var(--light-primary-text)); } } diff --git a/apps/client/src/styles/theme.scss b/apps/client/src/styles/theme.scss index fe9fd44a5..8dd6d8e36 100644 --- a/apps/client/src/styles/theme.scss +++ b/apps/client/src/styles/theme.scss @@ -1,9 +1,6 @@ @use '@angular/material' as mat; -$dark-primary-text: rgba(black, 0.87); -$light-primary-text: white; - -$mat-css-dark-theme-selector: '.theme-dark'; +@use './variables.scss' as variables; $gf-primary: ( 50: var(--gf-theme-primary-50), @@ -21,20 +18,20 @@ $gf-primary: ( A400: var(--gf-theme-primary-A400), A700: var(--gf-theme-primary-A700), contrast: ( - 50: $dark-primary-text, - 100: $dark-primary-text, - 200: $dark-primary-text, - 300: $light-primary-text, - 400: $light-primary-text, - 500: $light-primary-text, - 600: $light-primary-text, - 700: $light-primary-text, - 800: $light-primary-text, - 900: $light-primary-text, - A100: $dark-primary-text, - A200: $light-primary-text, - A400: $light-primary-text, - A700: $light-primary-text + 50: variables.$dark-primary-text, + 100: variables.$dark-primary-text, + 200: variables.$dark-primary-text, + 300: variables.$light-primary-text, + 400: variables.$light-primary-text, + 500: variables.$light-primary-text, + 600: variables.$light-primary-text, + 700: variables.$light-primary-text, + 800: variables.$light-primary-text, + 900: variables.$light-primary-text, + A100: variables.$dark-primary-text, + A200: variables.$light-primary-text, + A400: variables.$light-primary-text, + A700: variables.$light-primary-text ) ); @@ -54,20 +51,20 @@ $gf-secondary: ( A400: var(--gf-theme-secondary-A400), A700: var(--gf-theme-secondary-A700), contrast: ( - 50: $dark-primary-text, - 100: $dark-primary-text, - 200: $dark-primary-text, - 300: $light-primary-text, - 400: $light-primary-text, - 500: $light-primary-text, - 600: $light-primary-text, - 700: $light-primary-text, - 800: $light-primary-text, - 900: $light-primary-text, - A100: $dark-primary-text, - A200: $light-primary-text, - A400: $light-primary-text, - A700: $light-primary-text + 50: variables.$dark-primary-text, + 100: variables.$dark-primary-text, + 200: variables.$dark-primary-text, + 300: variables.$light-primary-text, + 400: variables.$light-primary-text, + 500: variables.$light-primary-text, + 600: variables.$light-primary-text, + 700: variables.$light-primary-text, + 800: variables.$light-primary-text, + 900: variables.$light-primary-text, + A100: variables.$dark-primary-text, + A200: variables.$light-primary-text, + A400: variables.$light-primary-text, + A700: variables.$light-primary-text ) ); diff --git a/apps/client/src/styles/variables.scss b/apps/client/src/styles/variables.scss new file mode 100644 index 000000000..dcf26eecc --- /dev/null +++ b/apps/client/src/styles/variables.scss @@ -0,0 +1,4 @@ +$dark-primary-text: rgba(black, 0.87); +$light-primary-text: white; + +$mat-css-dark-theme-selector: '.theme-dark'; From a5f934460bc4a79d5eaeb9a912b917a4743e7b5f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 2 Nov 2025 19:51:27 +0100 Subject: [PATCH 070/146] Task/introduce interface for get admin users response (#5903) * Introduce interface for get admin users response --- apps/api/src/app/admin/admin.controller.ts | 4 ++-- apps/api/src/app/admin/admin.service.ts | 6 +++--- .../app/components/admin-users/admin-users.component.ts | 8 ++++++-- .../user-detail-dialog/interfaces/interfaces.ts | 4 ++-- apps/client/src/app/services/admin.service.ts | 4 ++-- libs/common/src/lib/interfaces/index.ts | 4 ++-- .../admin-users-response.interface.ts} | 2 +- 7 files changed, 18 insertions(+), 14 deletions(-) rename libs/common/src/lib/interfaces/{admin-users.interface.ts => responses/admin-users-response.interface.ts} (88%) diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index d7c4c5d3d..2419b0a7d 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -17,7 +17,7 @@ import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { AdminData, AdminMarketData, - AdminUsers, + AdminUsersResponse, EnhancedSymbolProfile, ScraperConfiguration } from '@ghostfolio/common/interfaces'; @@ -315,7 +315,7 @@ export class AdminController { public async getUsers( @Query('skip') skip?: number, @Query('take') take?: number - ): Promise { + ): Promise { return this.adminService.getUsers({ skip: isNaN(skip) ? undefined : skip, take: isNaN(take) ? undefined : take diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 11f6f0599..683e72cb8 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -23,7 +23,7 @@ import { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, - AdminUsers, + AdminUsersResponse, AssetProfileIdentifier, EnhancedSymbolProfile, Filter @@ -513,7 +513,7 @@ export class AdminService { }: { skip?: number; take?: number; - }): Promise { + }): Promise { const [count, users] = await Promise.all([ this.countUsersWithAnalytics(), this.getUsersWithAnalytics({ skip, take }) @@ -818,7 +818,7 @@ export class AdminService { }: { skip?: number; take?: number; - }): Promise { + }): Promise { let orderBy: Prisma.Enumerable = [ { createdAt: 'desc' } ]; 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 c0d058ad2..94b5839c6 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 @@ -5,7 +5,11 @@ import { getDateFormatString, getEmojiFlag } from '@ghostfolio/common/helper'; -import { AdminUsers, InfoItem, User } from '@ghostfolio/common/interfaces'; +import { + AdminUsersResponse, + InfoItem, + User +} from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -75,7 +79,7 @@ import { GfUserDetailDialogComponent } from '../user-detail-dialog/user-detail-d export class GfAdminUsersComponent implements OnDestroy, OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; - public dataSource = new MatTableDataSource(); + public dataSource = new MatTableDataSource(); public defaultDateFormat: string; public deviceType: string; public displayedColumns: string[] = []; 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 5f3f4b87a..d29bc01bc 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,8 @@ -import { AdminUsers } from '@ghostfolio/common/interfaces'; +import { AdminUsersResponse } from '@ghostfolio/common/interfaces'; export interface UserDetailDialogParams { deviceType: string; hasPermissionForSubscription: boolean; locale: string; - userData: AdminUsers['users'][0]; + userData: AdminUsersResponse['users'][0]; } diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index a04ad8d56..2f3040ba3 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -12,7 +12,7 @@ import { AdminData, AdminJobs, AdminMarketData, - AdminUsers, + AdminUsersResponse, DataProviderGhostfolioStatusResponse, EnhancedSymbolProfile, Filter @@ -154,7 +154,7 @@ export class AdminService { params = params.append('skip', skip); params = params.append('take', take); - return this.http.get('/api/v1/admin/user', { params }); + return this.http.get('/api/v1/admin/user', { params }); } public gather7Days() { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index eac5db68c..06ecf32e8 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -7,7 +7,6 @@ import type { AdminMarketData, AdminMarketDataItem } from './admin-market-data.interface'; -import type { AdminUsers } from './admin-users.interface'; import type { AssetClassSelectorOption } from './asset-class-selector-option.interface'; import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { BenchmarkProperty } from './benchmark-property.interface'; @@ -39,6 +38,7 @@ import type { AccountBalancesResponse } from './responses/account-balances-respo import type { AccountsResponse } from './responses/accounts-response.interface'; import type { ActivitiesResponse } from './responses/activities-response.interface'; import type { ActivityResponse } from './responses/activity-response.interface'; +import type { AdminUsersResponse } from './responses/admin-users-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface'; import type { AssetResponse } from './responses/asset-response.interface'; @@ -92,7 +92,7 @@ export { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, - AdminUsers, + AdminUsersResponse, AiPromptResponse, ApiKeyResponse, AssetClassSelectorOption, diff --git a/libs/common/src/lib/interfaces/admin-users.interface.ts b/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts similarity index 88% rename from libs/common/src/lib/interfaces/admin-users.interface.ts rename to libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts index 79031425a..d9f58ee18 100644 --- a/libs/common/src/lib/interfaces/admin-users.interface.ts +++ b/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts @@ -1,6 +1,6 @@ import { Role } from '@prisma/client'; -export interface AdminUsers { +export interface AdminUsersResponse { count: number; users: { accountCount: number; From a892f51799205206359e74bc1f8de9dce11ee159 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:30:24 +0100 Subject: [PATCH 071/146] Task/improve localization in lib components (#5907) * Improve localization --- .../accounts-table.component.html | 2 +- libs/ui/src/lib/assistant/assistant.html | 22 +++++++++---------- .../lib/benchmark/benchmark.component.html | 4 ++-- 3 files changed, 13 insertions(+), 15 deletions(-) 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 e9e0337c9..805ffe77d 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.html +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -290,7 +290,7 @@ - +
diff --git a/libs/ui/src/lib/benchmark/benchmark.component.html b/libs/ui/src/lib/benchmark/benchmark.component.html index 2b75e5672..ab6db8887 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.html +++ b/libs/ui/src/lib/benchmark/benchmark.component.html @@ -8,8 +8,8 @@ [dataSource]="dataSource" > - - Name + + Name
From dfb4310904f9815820af4726da8721ad5adbedb9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:49:18 +0100 Subject: [PATCH 072/146] Task/improve localization of get started buttons (#5913) * Improve localization --- apps/client/src/app/components/header/header.component.html | 5 +++-- apps/client/src/app/pages/landing/landing-page.html | 6 ++---- apps/client/src/app/pages/pricing/pricing-page.html | 3 +-- .../resources/personal-finance-tools/product-page.html | 6 +++--- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/client/src/app/components/header/header.component.html b/apps/client/src/app/components/header/header.component.html index 27f136a3a..501119b31 100644 --- a/apps/client/src/app/components/header/header.component.html +++ b/apps/client/src/app/components/header/header.component.html @@ -427,10 +427,11 @@ Get started - + >Get Started } diff --git a/apps/client/src/app/pages/landing/landing-page.html b/apps/client/src/app/pages/landing/landing-page.html index 7f77c31a7..50602858a 100644 --- a/apps/client/src/app/pages/landing/landing-page.html +++ b/apps/client/src/app/pages/landing/landing-page.html @@ -38,9 +38,8 @@ i18n mat-flat-button [routerLink]="routerLinkRegister" + >Get Started - Get Started - } @if (hasPermissionForDemo) { @if (hasPermissionToCreateUser) { @@ -342,9 +341,8 @@ i18n mat-flat-button [routerLink]="routerLinkRegister" + >Get Started - Get Started - @if (hasPermissionForDemo) {
or
Get Started - Get Started -

It’s free.

diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html index 3c5c97558..a71ca0038 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -330,9 +330,9 @@ Ghostfolio.

From c84f4bd5e6360db661aa7886e2dc516cc491c8f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:03:09 +0100 Subject: [PATCH 073/146] Feature/update locales (#5875) * Update locales * 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 | 228 ++++++++++-------------- apps/client/src/locales/messages.de.xlf | 220 ++++++++++------------- apps/client/src/locales/messages.es.xlf | 220 ++++++++++------------- apps/client/src/locales/messages.fr.xlf | 228 ++++++++++-------------- apps/client/src/locales/messages.it.xlf | 220 ++++++++++------------- apps/client/src/locales/messages.nl.xlf | 220 ++++++++++------------- apps/client/src/locales/messages.pl.xlf | 228 ++++++++++-------------- apps/client/src/locales/messages.pt.xlf | 220 ++++++++++------------- apps/client/src/locales/messages.tr.xlf | 228 ++++++++++-------------- apps/client/src/locales/messages.uk.xlf | 228 ++++++++++-------------- apps/client/src/locales/messages.xlf | 221 ++++++++++------------- apps/client/src/locales/messages.zh.xlf | 228 ++++++++++-------------- 13 files changed, 1133 insertions(+), 1557 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ecf2a57d..0ff76b0c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed the build executor of the client from `@nx/angular:webpack-browser` to `@nx/angular:browser-esbuild` +- Improved the language localization for German (`de`) ### Fixed diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 7c676e092..361c4ec31 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -42,7 +42,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -398,14 +398,6 @@ 86 - - Holdings - En cartera - - libs/ui/src/lib/assistant/assistant.html - 110 - - Cash Balances Balanç de Caixa @@ -473,6 +465,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -1475,7 +1471,7 @@ Està segur que vol eliminar aquest usuari? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -1646,14 +1642,6 @@ 5 - - Get started - Primers Passos - - apps/client/src/app/components/header/header.component.html - 432 - - Oops! Incorrect Security Token. Oooh! El testimoni de seguretat és incorrecte. @@ -1747,7 +1735,7 @@ Informar d’un Problema amb les Dades apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1991,11 +1979,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -2018,20 +2006,12 @@ 30 - - Sign in with Internet Identity - Inicieu la sessió amb la identitat d’Internet - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Inicieu la sessió amb Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -2039,7 +2019,7 @@ Manteniu la sessió iniciada apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -2889,6 +2869,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Oops, cash balance transfer has failed. @@ -3334,14 +3318,34 @@ Get Started Comença + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Holdings @@ -3366,6 +3370,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -3479,32 +3487,12 @@ 11 - - Get Started - Comença - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - Monthly Active Users Usuaris actius mensuals apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -3512,7 +3500,7 @@ Estrelles a GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -3524,7 +3512,7 @@ Activa el Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -3536,7 +3524,7 @@ Com es veu a apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -3544,7 +3532,7 @@ Protegeix els teus actius. Refina la teva estratègia d’inversió personal. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -3552,7 +3540,7 @@ Ghostfolio permet a la gent ocupada fer un seguiment d’accions, ETF o criptomonedes sense ser rastrejada. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -3560,7 +3548,7 @@ Vista de 360° apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -3568,7 +3556,7 @@ Obtingueu la imatge completa de les vostres finances personals en múltiples plataformes. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -3576,7 +3564,7 @@ Web3 llest apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -3584,7 +3572,7 @@ Utilitza Ghostfolio de manera anònima i sigues propietari de les teves dades financeres. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -3592,7 +3580,7 @@ Beneficia’t de millores contínues gràcies a una comunitat forta. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -3608,7 +3596,7 @@ Per què Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -3616,7 +3604,7 @@ Ghostfolio és per a tu si ets... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -3624,7 +3612,7 @@ negociar accions, ETF o criptomonedes en múltiples plataformes apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -3632,7 +3620,7 @@ perseguint una compra & mantenir l’estratègia apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -3640,7 +3628,7 @@ interessat a obtenir informació sobre la composició de la vostra cartera apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -3648,7 +3636,7 @@ valorant la privadesa i la propietat de les dades apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -3656,7 +3644,7 @@ al minimalisme apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -3664,7 +3652,7 @@ preocupant-se per diversificar els seus recursos econòmics apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -3672,7 +3660,7 @@ interessada en la independència financera apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -3680,7 +3668,7 @@ dir no als fulls de càlcul apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -3688,7 +3676,7 @@ encara llegint aquesta llista apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -3696,7 +3684,7 @@ Més informació sobre Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -3704,7 +3692,7 @@ Que nostre usuaris estan dient apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -3712,7 +3700,7 @@ Membres de tot el món estan utilitzant Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -3720,7 +3708,7 @@ Com ho fa Ghostfolio treballar? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -3728,7 +3716,7 @@ Comença en només 3 passos apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -3744,7 +3732,7 @@ Registra’t de manera anònima* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -3752,7 +3740,7 @@ * no es requereix cap adreça de correu electrònic ni targeta de crèdit apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -3760,7 +3748,7 @@ Afegiu qualsevol de les vostres transaccions històriques apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -3768,7 +3756,7 @@ Obteniu informació valuosa sobre la composició de la vostra cartera apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -3776,7 +3764,7 @@ Són tu llest? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -3784,7 +3772,7 @@ Uneix-te ara o consulteu el compte d’exemple apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -4460,7 +4448,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -4788,7 +4776,7 @@ És gratuït. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4843,20 +4831,12 @@ 281 - - Continue with Internet Identity - Continueu amb Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Continueu amb Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -5439,6 +5419,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Date Range @@ -5448,20 +5432,20 @@ 170 - + Reset Filters Restableix els filtres libs/ui/src/lib/assistant/assistant.html - 205 + 204 - + Apply Filters Aplicar filtres libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -5833,7 +5817,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -6209,11 +6193,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -6329,7 +6313,7 @@ Open Source apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6719,10 +6703,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,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 - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Name - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Quick Links @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Asset Profiles - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Live Demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index fa47cc6a6..9a0ebd159 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -144,6 +144,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -376,6 +380,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -646,7 +654,7 @@ Möchtest du diesen Benutzer wirklich löschen? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -700,14 +708,34 @@ Get Started Registrieren + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Sign in @@ -722,7 +750,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -810,11 +838,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -837,20 +865,12 @@ 30 - - Sign in with Internet Identity - Einloggen mit Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Einloggen mit Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -858,7 +878,7 @@ Eingeloggt bleiben apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -990,7 +1010,7 @@ Datenfehler melden apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1344,6 +1364,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -1840,10 +1864,6 @@ libs/common/src/lib/routes/routes.ts 167 - - - Holdings - Positionen libs/ui/src/lib/assistant/assistant.html 110 @@ -2117,20 +2137,12 @@ 281 - - Continue with Internet Identity - Weiter mit Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Weiter mit Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -2309,14 +2321,6 @@ 56 - - Get started - Registrieren - - apps/client/src/app/components/header/header.component.html - 432 - - This feature is currently unavailable. Diese Funktion ist derzeit nicht verfügbar. @@ -2906,7 +2910,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -2926,11 +2930,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3537,32 +3541,12 @@ 303 - - Get Started - Jetzt loslegen - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - It’s free. Es ist kostenlos. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4598,7 +4582,7 @@ Sterne auf GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -4610,7 +4594,7 @@ Downloads auf Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -4730,7 +4714,7 @@ Monatlich aktive Nutzer apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -4738,7 +4722,7 @@ Bekannt aus apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -4746,7 +4730,7 @@ Schütze dein Vermögen. Optimiere deine persönliche Anlagestrategie. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -4754,7 +4738,7 @@ Ghostfolio ermöglicht es geschäftigen Leuten, den Überblick über Aktien, ETFs oder Kryptowährungen zu behalten, ohne überwacht zu werden. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -4762,7 +4746,7 @@ 360° Ansicht apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -4770,7 +4754,7 @@ Web3 ready apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -4778,7 +4762,7 @@ Nutze Ghostfolio ganz anonym und behalte deine Finanzdaten. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -4786,7 +4770,7 @@ Profitiere von kontinuierlichen Verbesserungen durch eine aktive Community. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -4802,7 +4786,7 @@ Warum Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -4810,7 +4794,7 @@ Ghostfolio ist für dich geeignet, wenn du... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -4818,7 +4802,7 @@ Aktien, ETFs oder Kryptowährungen auf unterschiedlichen Plattformen handelst apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -4826,7 +4810,7 @@ eine Buy & Hold Strategie verfolgst apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -4834,7 +4818,7 @@ dich für die Zusammensetzung deines Portfolios interessierst apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -4842,7 +4826,7 @@ Privatsphäre und Datenhoheit wertschätzt apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -4850,7 +4834,7 @@ zum Frugalismus oder Minimalismus neigst apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -4858,7 +4842,7 @@ dich um die Diversifizierung deiner finanziellen Mittel kümmerst apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -4866,7 +4850,7 @@ Interesse an finanzieller Freiheit hast apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -4874,7 +4858,7 @@ Nein sagst zu Excel-Tabellen im Jahr apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -4882,7 +4866,7 @@ diese Liste bis zum Ende liest apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -4890,7 +4874,7 @@ Erfahre mehr über Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4898,7 +4882,7 @@ Was unsere Nutzer sagen apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4906,7 +4890,7 @@ Nutzer aus aller Welt verwenden Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4914,7 +4898,7 @@ Wie funktioniert Ghostfolio ? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4922,7 +4906,7 @@ Registriere dich anonym* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4930,7 +4914,7 @@ * Keine E-Mail-Adresse oder Kreditkarte erforderlich apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4938,7 +4922,7 @@ Füge historische Transaktionen hinzu apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4946,7 +4930,7 @@ Erhalte nützliche Erkenntnisse über die Zusammensetzung deines Portfolios apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4954,7 +4938,7 @@ Bist du bereit? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4962,7 +4946,7 @@ Verschaffe dir einen vollständigen Überblick deiner persönlichen Finanzen über mehrere Plattformen hinweg. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -4970,7 +4954,7 @@ Beginne mit nur 3 Schritten apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -5788,7 +5772,7 @@ Position abschliessen apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5907,12 +5891,12 @@ 8 - + Reset Filters Filter zurücksetzen libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5947,12 +5931,12 @@ 411 - + Apply Filters Filter anwenden libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6177,7 +6161,7 @@ Melde dich jetzt an oder probiere die Live Demo aus apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6353,7 +6337,7 @@ Open Source apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6743,10 +6727,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7419,7 +7399,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7439,11 +7419,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7539,7 +7519,7 @@ Sicherheits-Token apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7551,7 +7531,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 - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Name - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Schnellzugriff @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Anlageprofile - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Live Demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Anlageprofil verwalten apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 4c380b007..07e4e855d 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -145,6 +145,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -377,6 +381,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -631,7 +639,7 @@ ¿Estás seguro de eliminar este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -685,14 +693,34 @@ Get Started Empezar + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Sign in @@ -707,7 +735,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -795,11 +823,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -822,20 +850,12 @@ 30 - - Sign in with Internet Identity - Iniciar sesión con Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Iniciar sesión con Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -843,7 +863,7 @@ Seguir conectado apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -975,7 +995,7 @@ Reporta un anomalía de los datos apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1329,6 +1349,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -1825,10 +1849,6 @@ libs/common/src/lib/routes/routes.ts 167 - - - Holdings - Participaciones libs/ui/src/lib/assistant/assistant.html 110 @@ -2102,20 +2122,12 @@ 281 - - Continue with Internet Identity - Continuar con Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Continuar con Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -2294,14 +2306,6 @@ 56 - - Get started - Comenzar - - apps/client/src/app/components/header/header.component.html - 432 - - This feature is currently unavailable. Esta funcionalidad no está disponible actualmente. @@ -2891,7 +2895,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -2911,11 +2915,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3522,32 +3526,12 @@ 303 - - Get Started - Empieza - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - It’s free. Es gratis. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4575,7 +4559,7 @@ Estrellas en GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -4587,7 +4571,7 @@ Descargas en Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -4707,7 +4691,7 @@ Usuarios activos mensuales apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -4715,7 +4699,7 @@ Visto en apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -4723,7 +4707,7 @@ Protege tus assets. Mejora tu estrategia de inversión personal. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -4731,7 +4715,7 @@ Ghostfolio permite a las personas ocupadas hacer un seguimiento de acciones, ETFs o criptomonedas sin ser rastreadas. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -4739,7 +4723,7 @@ Vista 360° apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -4747,7 +4731,7 @@ Preparado para Web3 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -4755,7 +4739,7 @@ Usa Ghostfolio de forma anónima y sé dueño de tus datos financieros. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -4763,7 +4747,7 @@ Disfruta de mejoras continuas gracias a una comunidad sólida. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -4779,7 +4763,7 @@ ¿Por qué Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -4787,7 +4771,7 @@ Ghostfolio es para ti si estás... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -4795,7 +4779,7 @@ operando con acciones, ETFs o criptomonedas en múltiples plataformas apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -4803,7 +4787,7 @@ persiguiendo una compra & mantener estrategia apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -4811,7 +4795,7 @@ interesado en obtener información sobre la composición de tu portafolio apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -4819,7 +4803,7 @@ valorando la privacidad y la propiedad de tus datos apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -4827,7 +4811,7 @@ en el minimalismo apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -4835,7 +4819,7 @@ preocuparse por diversificar tus recursos financieros apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -4843,7 +4827,7 @@ interesado en la independencia financiera apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -4851,7 +4835,7 @@ diciendo no a las hojas de cálculo en apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -4859,7 +4843,7 @@ todavía leyendo esta lista apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -4867,7 +4851,7 @@ Más información sobre Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4875,7 +4859,7 @@ Lo que nuestros usuarios están diciendo apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4883,7 +4867,7 @@ Miembros de todo el mundo están usando Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4891,7 +4875,7 @@ ¿Cómo Ghostfolio work? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4899,7 +4883,7 @@ Regístrate de forma anónima* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4907,7 +4891,7 @@ * no se requiere dirección de correo electrónico ni tarjeta de crédito apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4915,7 +4899,7 @@ Agrega cualquiera de tus transacciones históricas apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4923,7 +4907,7 @@ Obtén información valiosa sobre la composición de tu portafolio apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4931,7 +4915,7 @@ ¿Estás listo? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4939,7 +4923,7 @@ Obtén una visión completa de tus finanzas personales en múltiples plataformas. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -4947,7 +4931,7 @@ Comienza en solo 3 pasos apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -5765,7 +5749,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5884,12 +5868,12 @@ 8 - + Reset Filters Reiniciar filtros libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5924,12 +5908,12 @@ 411 - + Apply Filters Aplicar filtros libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6154,7 +6138,7 @@ Únete ahora o consulta la cuenta de ejemplo apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6330,7 +6314,7 @@ Código abierto apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6720,10 +6704,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7396,7 +7376,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7416,11 +7396,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7516,7 +7496,7 @@ Token de seguridad apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7528,7 +7508,7 @@ ¿Realmente deseas generar un nuevo token de seguridad para este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 233 + 237 @@ -7805,15 +7785,7 @@ 158 - - Name - Nombre - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Enlaces rápidos @@ -7821,24 +7793,16 @@ 58 - - Asset Profiles - Perfiles de activos - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Demostración en vivo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8193,7 +8157,7 @@ Gestionar perfil de activo apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index b25a87570..f51609582 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -152,6 +152,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -432,6 +436,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -838,7 +846,7 @@ Voulez-vous vraiment supprimer cet·te utilisateur·rice ? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -933,14 +941,6 @@ 5 - - Get started - Démarrer - - apps/client/src/app/components/header/header.component.html - 432 - - Sign in Se connecter @@ -954,7 +954,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -1090,11 +1090,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1117,20 +1117,12 @@ 30 - - Sign in with Internet Identity - Se connecter avec Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Se connecter avec Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -1138,7 +1130,7 @@ Rester connecté apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -1262,7 +1254,7 @@ Signaler une Erreur de Données apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1676,6 +1668,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -1912,6 +1908,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -2425,14 +2425,6 @@ 7 - - Holdings - Positions - - libs/ui/src/lib/assistant/assistant.html - 110 - - Pricing Prix @@ -2500,14 +2492,34 @@ Get Started Démarrer + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Registration @@ -2541,20 +2553,12 @@ 101 - - Continue with Internet Identity - Continue avec Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Continuer avec Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -2922,7 +2926,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -3150,11 +3154,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3521,32 +3525,12 @@ 303 - - Get Started - Démarrer - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - It’s free. C’est gratuit. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4574,7 +4558,7 @@ Etoiles sur GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -4586,7 +4570,7 @@ Téléchargement depuis Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -4706,7 +4690,7 @@ Utilisateurs actifs mensuels apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -4714,7 +4698,7 @@ Médias apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -4722,7 +4706,7 @@ Protégez vos actifs. Affinez votre stratégie d’investissement personnelle.. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -4730,7 +4714,7 @@ Ghostfolio permet aux personnes occupées de suivre ses actions, ETF ou cryptomonnaies sans être pistées. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -4738,7 +4722,7 @@ Vision 360° apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -4746,7 +4730,7 @@ Compatible Web3 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -4754,7 +4738,7 @@ Utilisez Ghostfolio de manière anonyme et soyez propriétaire de vos données financières. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -4762,7 +4746,7 @@ Bénéficiez d’améliorations continues grâce à une communauté impliquée. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -4778,7 +4762,7 @@ Pourquoi Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -4786,7 +4770,7 @@ Ghostfolio est pour vous si vous ... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -4794,7 +4778,7 @@ tradez des actions, ETFs or crypto-monnaies sur plusieurs plateforme. apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -4802,7 +4786,7 @@ adoptez une stratégie Buy & and Hold apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -4810,7 +4794,7 @@ êtes intéressés d’avoir un aperçu de la composition de votre portefeuille apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -4818,7 +4802,7 @@ valorisez la confidentialité et la propriété de vos données apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -4826,7 +4810,7 @@ êtes minimaliste apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -4834,7 +4818,7 @@ vous souciez de diversifier vos ressources financières apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -4842,7 +4826,7 @@ êtes intéressés d’atteindre l’indépendance financière apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -4850,7 +4834,7 @@ dites non aux feuilles de calcul en apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -4858,7 +4842,7 @@ continuez à lire cette liste apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -4866,7 +4850,7 @@ En appendre plus sur Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4874,7 +4858,7 @@ Qu’en pensent nos utilisateurs apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4882,7 +4866,7 @@ Les utilisateurs du monde entier utilisent Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4890,7 +4874,7 @@ Comment fonctionne Ghostfolio ? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4898,7 +4882,7 @@ Inscrivez-vous de manière anonyme* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4906,7 +4890,7 @@ * aucune adresse mail ni carte de crédit requise apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4914,7 +4898,7 @@ Ajoutez l’une de vos transactions historiques apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4922,7 +4906,7 @@ Obtenez de précieuses informations sur la composition de votre portefeuille apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4930,7 +4914,7 @@ Êtes- vous prêts ? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4938,7 +4922,7 @@ Obtenez une vue d’ensemble de vos finances personnelles sur plusieurs plateformes. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -4946,7 +4930,7 @@ Démarrer en seulement 3 étapes apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -5764,7 +5748,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5883,12 +5867,12 @@ 8 - + Reset Filters Réinitialiser les Filtres libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5923,12 +5907,12 @@ 411 - + Apply Filters Appliquer les Filtres libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6153,7 +6137,7 @@ Rejoindre ou voir un compte démo apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6329,7 +6313,7 @@ Open Source apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6719,10 +6703,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Jeton de sécurité apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,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 - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Nom - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Liens rapides @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Profils d’Actifs - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Démo Live apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Gérer le profil d’actif apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index f5c8f799c..5b4058606 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -145,6 +145,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -377,6 +381,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -631,7 +639,7 @@ Vuoi davvero eliminare questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -685,14 +693,34 @@ Get Started Inizia + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Sign in @@ -707,7 +735,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -795,11 +823,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -822,20 +850,12 @@ 30 - - Sign in with Internet Identity - Accedi con Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Accedi con Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -843,7 +863,7 @@ Rimani connesso apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -975,7 +995,7 @@ Segnala un’anomalia dei dati apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1329,6 +1349,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -1825,10 +1849,6 @@ libs/common/src/lib/routes/routes.ts 167 - - - Holdings - Partecipazioni libs/ui/src/lib/assistant/assistant.html 110 @@ -2102,20 +2122,12 @@ 281 - - Continue with Internet Identity - Continua con Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Continua con Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -2294,14 +2306,6 @@ 56 - - Get started - Inizia - - apps/client/src/app/components/header/header.component.html - 432 - - This feature is currently unavailable. Questa funzionalità non è attualmente disponibile. @@ -2891,7 +2895,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -2911,11 +2915,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3522,32 +3526,12 @@ 303 - - Get Started - Inizia - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - It’s free. È gratuito. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4575,7 +4559,7 @@ Stelle su GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -4587,7 +4571,7 @@ Estrazioni su Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -4707,7 +4691,7 @@ Utenti attivi mensili apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -4715,7 +4699,7 @@ Come si vede su apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -4723,7 +4707,7 @@ Proteggi i tuoi asset. Perfeziona la tua strategia di investimento personale. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -4731,7 +4715,7 @@ Ghostfolio permette alle persone impegnate di tenere traccia di azioni, ETF o criptovalute senza essere tracciate. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -4739,7 +4723,7 @@ Vista a 360° apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -4747,7 +4731,7 @@ Pronto per il Web3 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -4755,7 +4739,7 @@ Usa Ghostfolio in modo anonimo e possiedi i tuoi dati finanziari. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -4763,7 +4747,7 @@ Beneficia dei continui miglioramenti grazie a una forte comunità. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -4779,7 +4763,7 @@ Perché Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -4787,7 +4771,7 @@ Ghostfolio è per te se... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -4795,7 +4779,7 @@ fai trading di azioni, ETF o criptovalute su più piattaforme apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -4803,7 +4787,7 @@ persegui una strategia buy & hold apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -4811,7 +4795,7 @@ sei interessato a conoscere la composizione del tuo portafoglio apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -4819,7 +4803,7 @@ valorizzi la privacy e la proprietà dei dati apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -4827,7 +4811,7 @@ sei per il minimalismo apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -4835,7 +4819,7 @@ ti interessa diversificare le tue risorse finanziarie apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -4843,7 +4827,7 @@ sei interessato all’indipendenza finanziaria apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -4851,7 +4835,7 @@ non vuoi utilizzare il foglio elettronico nel apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -4859,7 +4843,7 @@ stai ancora leggendo questo elenco apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -4867,7 +4851,7 @@ Ulteriori informazioni su Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4875,7 +4859,7 @@ Cosa dicono i nostri utenti apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4883,7 +4867,7 @@ Membri da tutto il mondo utilizzano Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4891,7 +4875,7 @@ Come funziona Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4899,7 +4883,7 @@ Iscriviti in modo anonimo* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4907,7 +4891,7 @@ * non è richiesto alcun indirizzo email né la carta di credito apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4915,7 +4899,7 @@ Aggiungi le tue transazioni storiche apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4923,7 +4907,7 @@ Ottieni informazioni preziose sulla composizione del tuo portafoglio apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4931,7 +4915,7 @@ Sei pronto? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4939,7 +4923,7 @@ Ottieni un quadro completo delle tue finanze personali su più piattaforme. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -4947,7 +4931,7 @@ Inizia in soli 3 passi apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -5765,7 +5749,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5884,12 +5868,12 @@ 8 - + Reset Filters Reset Filtri libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5924,12 +5908,12 @@ 411 - + Apply Filters Applica i Filtri libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6154,7 +6138,7 @@ Registrati adesso o prova l’account demo apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6330,7 +6314,7 @@ Open Source apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6720,10 +6704,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7396,7 +7376,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7416,11 +7396,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7516,7 +7496,7 @@ Token di sicurezza apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7528,7 +7508,7 @@ Vuoi davvero generare un nuovo token di sicurezza per questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 233 + 237 @@ -7805,15 +7785,7 @@ 158 - - Name - Nome - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Collegamenti rapidi @@ -7821,24 +7793,16 @@ 58 - - Asset Profiles - Profili delle risorse - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Dimostrazione dal vivo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8193,7 +8157,7 @@ Gestisci profilo risorsa apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 1ec127b22..6a1f871a6 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -144,6 +144,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -376,6 +380,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -630,7 +638,7 @@ Wilt je deze gebruiker echt verwijderen? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -684,14 +692,34 @@ Get Started Aan de slag + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Sign in @@ -706,7 +734,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -794,11 +822,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -821,20 +849,12 @@ 30 - - Sign in with Internet Identity - Aanmelden met Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Aanmelden met Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -842,7 +862,7 @@ Aangemeld blijven apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -974,7 +994,7 @@ Gegevensstoring melden apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1328,6 +1348,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -1824,10 +1848,6 @@ libs/common/src/lib/routes/routes.ts 167 - - - Holdings - Posities libs/ui/src/lib/assistant/assistant.html 110 @@ -2101,20 +2121,12 @@ 281 - - Continue with Internet Identity - Ga verder met Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Verder met Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -2293,14 +2305,6 @@ 56 - - Get started - Aan de slag - - apps/client/src/app/components/header/header.component.html - 432 - - This feature is currently unavailable. Deze functie is momenteel niet beschikbaar. @@ -2890,7 +2894,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -2910,11 +2914,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3521,32 +3525,12 @@ 303 - - Get Started - Aan de slag - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - It’s free. Het is gratis. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4574,7 +4558,7 @@ Sterren op GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -4586,7 +4570,7 @@ Pulls op Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -4706,7 +4690,7 @@ Maandelijkse actieve gebruikers apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -4714,7 +4698,7 @@ Zoals te zien in apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -4722,7 +4706,7 @@ Bescherm je financiële bezittingen. Verfijn je persoonlijke investeringsstrategie. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -4730,7 +4714,7 @@ Ghostfolio stelt drukbezette mensen in staat om aandelen, ETF’s of cryptocurrencies bij te houden zonder gevolgd te worden. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -4738,7 +4722,7 @@ 360°-overzicht apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -4746,7 +4730,7 @@ Klaar voor Web3 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -4754,7 +4738,7 @@ Gebruik Ghostfolio anoniem en bezit je financiële gegevens. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -4762,7 +4746,7 @@ Profiteer van voortdurende verbeteringen door een sterke gemeenschap. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -4778,7 +4762,7 @@ Waarom Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -4786,7 +4770,7 @@ Ghostfolio is iets voor je als je... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -4794,7 +4778,7 @@ handelt in aandelen, ETF’s of cryptocurrencies op meerdere platforms apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -4802,7 +4786,7 @@ streeft naar een buy & hold strategie apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -4810,7 +4794,7 @@ geïnteresseerd bent in het krijgen van inzicht in je portefeuillesamenstelling apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -4818,7 +4802,7 @@ privacy en eigendom van gegevens waardeert apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -4826,7 +4810,7 @@ houdt van een minimalistisch ontwerp apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -4834,7 +4818,7 @@ zorgdraagt voor het diversifiëren van je financiële middelen apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -4842,7 +4826,7 @@ geïnteresseerd bent in financiële onafhankelijkheid apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -4850,7 +4834,7 @@ "nee" zegt tegen spreadsheets in apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -4858,7 +4842,7 @@ nog steeds deze lijst aan het lezen bent apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -4866,7 +4850,7 @@ Leer meer over Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4874,7 +4858,7 @@ Wat onze gebruikers zeggen apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4882,7 +4866,7 @@ Leden van over de hele wereld gebruikenGhostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4890,7 +4874,7 @@ Hoe Ghostfolio werkt? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4898,7 +4882,7 @@ Anoniem aanmelden* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4906,7 +4890,7 @@ * geen e-mailadres of creditcard nodig apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4914,7 +4898,7 @@ Voeg al je historische transacties toe apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4922,7 +4906,7 @@ Krijg waardevolle inzichten in de samenstelling van je portefeuille apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4930,7 +4914,7 @@ Ben jij er klaar voor? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4938,7 +4922,7 @@ Krijg een volledig beeld van je persoonlijke financiën op meerdere platforms. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -4946,7 +4930,7 @@ Aan de slag in slechts 3 stappen apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -5764,7 +5748,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5883,12 +5867,12 @@ 8 - + Reset Filters Filters Herstellen libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5923,12 +5907,12 @@ 411 - + Apply Filters Filters Toepassen libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6153,7 +6137,7 @@ Word nu lid of bekijk het voorbeeldaccount apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6329,7 +6313,7 @@ Open Source apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6719,10 +6703,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Beveiligingstoken apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,7 @@ Wilt u echt een nieuw beveiligingstoken voor deze gebruiker aanmaken? apps/client/src/app/components/admin-users/admin-users.component.ts - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Naam - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Snelle koppelingen @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Activaprofielen - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Live-demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Beheer activaprofiel apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index d8e342569..a52b15599 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -389,6 +389,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -1295,7 +1299,7 @@ Czy na pewno chcesz usunąć tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -1422,14 +1426,6 @@ 5 - - Get started - Rozpocznij - - apps/client/src/app/components/header/header.component.html - 432 - - Sign in Zaloguj się @@ -1443,7 +1439,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -1679,11 +1675,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1706,20 +1702,12 @@ 30 - - Sign in with Internet Identity - Zaloguj się przy użyciu Tożsamości Internetowej (Internet Identity) - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Zaloguj się przez Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -1727,7 +1715,7 @@ Pozostań zalogowany apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -1883,7 +1871,7 @@ Zgłoś Błąd Danych apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -2557,6 +2545,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Oops, cash balance transfer has failed. @@ -2969,14 +2961,34 @@ Get Started Rozpocznij + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Holdings @@ -3001,6 +3013,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -3106,32 +3122,12 @@ 11 - - Get Started - Rozpocznij - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - Monthly Active Users Aktywni Użytkownicy w Miesiącu apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -3139,7 +3135,7 @@ Gwiazdki na GitHubie apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -3151,7 +3147,7 @@ Pobrania na Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -3163,7 +3159,7 @@ Dostrzegli Nas apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -3171,7 +3167,7 @@ Chroń swoje zasoby. Udoskonal swoją osobistą strategię inwestycyjną. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -3179,7 +3175,7 @@ Ghostfolio umożliwia zapracowanym osobom śledzenie akcji, funduszy ETF lub kryptowalut, jednocześnie zachowując prywatność. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -3187,7 +3183,7 @@ Widok 360° apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -3195,7 +3191,7 @@ Uzyskaj pełny obraz swoich finansów osobistych na wielu różnych platformach. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -3203,7 +3199,7 @@ Gotowy na Web3 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -3211,7 +3207,7 @@ Korzystaj z Ghostfolio anonimowo i zachowaj pełną kontrolę nad swoimi danymi finansowymi. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -3219,7 +3215,7 @@ Czerp korzyści z nieustannych ulepszeń dzięki silnej społeczności. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -3235,7 +3231,7 @@ Dlaczego Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -3243,7 +3239,7 @@ Ghostfolio jest dla Ciebie, jeśli... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -3251,7 +3247,7 @@ handlujesz akcjami, funduszami ETF lub kryptowalutami na wielu platformach apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -3259,7 +3255,7 @@ realizujesz strategię buy & hold apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -3267,7 +3263,7 @@ chcesz uzyskać wgląd w strukturę swojego portfolio apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -3275,7 +3271,7 @@ cenisz sobie prywatność i własność swoich danych apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -3283,7 +3279,7 @@ lubisz minimalizm apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -3291,7 +3287,7 @@ zależy Ci na dywersyfikacji swoich zasobów finansowych apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -3299,7 +3295,7 @@ jesteś zainteresowany niezależnością finansową apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -3307,7 +3303,7 @@ mówisz „nie” arkuszom kalkulacyjnym w roku apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -3315,7 +3311,7 @@ nadal czytasz tę listę apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -3323,7 +3319,7 @@ Dowiedz się więcej o Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -3331,7 +3327,7 @@ Co mówią nasi użytkownicy apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -3339,7 +3335,7 @@ Użytkownicy z całego świata korzystają z Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -3347,7 +3343,7 @@ Jak działa Ghostfolio ? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -3355,7 +3351,7 @@ Rozpocznij w zaledwie 3 krokach apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -3371,7 +3367,7 @@ Zarejestruj się anonimowo* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -3379,7 +3375,7 @@ * nie jest wymagany ani adres e-mail, ani karta kredytowa apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -3387,7 +3383,7 @@ Dodaj dowolne z Twoich historycznych transakcji apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -3395,7 +3391,7 @@ Zyskaj cenny wgląd w strukturę swojego portfolio apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -3403,7 +3399,7 @@ Czy jesteś gotów? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4114,14 +4110,6 @@ 7 - - Holdings - Inwestycje - - libs/ui/src/lib/assistant/assistant.html - 110 - - Pricing Cennik @@ -4343,7 +4331,7 @@ Jest bezpłatny. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4390,20 +4378,12 @@ 281 - - Continue with Internet Identity - Kontynuuj z tożsamością internetową - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Zaloguj z Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -4870,6 +4850,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + , @@ -5208,7 +5192,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -5576,11 +5560,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5764,7 +5748,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5883,12 +5867,12 @@ 8 - + Reset Filters Resetuj Filtry libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5923,12 +5907,12 @@ 411 - + Apply Filters Zastosuj Filtry libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6153,7 +6137,7 @@ Dołącz teraz lub sprawdź przykładowe konto apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6329,7 +6313,7 @@ Otwarty Kod Źródłowy apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6719,10 +6703,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Token bezpieczeństwa apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,7 @@ Czy napewno chcesz wygenerować nowy token bezpieczeństwa dla tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Nazwa - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Szybkie linki @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Profile zasobów - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Demonstracja na żywo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Zarządzaj profilem aktywów apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 9280de1dd..3867d6fbe 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -152,6 +152,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -432,6 +436,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -710,7 +718,7 @@ Deseja realmente excluir este utilizador? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -805,14 +813,6 @@ 5 - - Get started - Começar - - apps/client/src/app/components/header/header.component.html - 432 - - Sign in Iniciar sessão @@ -826,7 +826,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -970,11 +970,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -997,20 +997,12 @@ 30 - - Sign in with Internet Identity - Iniciar sessão com Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Iniciar sessão com Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -1018,7 +1010,7 @@ Manter sessão iniciada apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -1222,7 +1214,7 @@ Dados do Relatório com Problema apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -1672,6 +1664,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -2360,10 +2356,6 @@ libs/common/src/lib/routes/routes.ts 167 - - - Holdings - Posições libs/ui/src/lib/assistant/assistant.html 110 @@ -2436,14 +2428,34 @@ Get Started Começar + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Registration @@ -2477,20 +2489,12 @@ 101 - - Continue with Internet Identity - Continuar com Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Continuar com Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -2766,7 +2770,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -2994,11 +2998,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3521,32 +3525,12 @@ 303 - - Get Started - Começar - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - It’s free. É gratuito. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4574,7 +4558,7 @@ Estrelas no GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -4586,7 +4570,7 @@ Não puxa Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -4706,7 +4690,7 @@ Usuários ativos mensais apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -4714,7 +4698,7 @@ Como visto em apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -4722,7 +4706,7 @@ Proteja o seu assets. Refine your personal investment strategy. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -4730,7 +4714,7 @@ O Ghostfolio permite que pessoas ocupadas acompanhem ações, ETFs ou criptomoedas sem serem rastreadas. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -4738,7 +4722,7 @@ 360° visualizar apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -4746,7 +4730,7 @@ Web3 Preparar apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -4754,7 +4738,7 @@ Use o Ghostfolio anonimamente e possua seus dados financeiros. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -4762,7 +4746,7 @@ Beneficie-se de melhorias contínuas através de uma comunidade forte. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -4778,7 +4762,7 @@ Por que Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -4786,7 +4770,7 @@ Ghostfolio é para você se você for... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -4794,7 +4778,7 @@ negociar ações, ETFs ou criptomoedas em múltiplas plataformas apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -4802,7 +4786,7 @@ buscando uma compra & estratégia de retenção apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -4810,7 +4794,7 @@ interessado em obter insights sobre a composição do seu portfólio apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -4818,7 +4802,7 @@ valorizando a privacidade e a propriedade dos dados apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -4826,7 +4810,7 @@ no minimalismo apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -4834,7 +4818,7 @@ preocupando-se em diversificar seus recursos financeiros apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -4842,7 +4826,7 @@ interessado em independência financeira apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -4850,7 +4834,7 @@ dizendo não às planilhas em apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -4858,7 +4842,7 @@ ainda lendo esta lista apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -4866,7 +4850,7 @@ Saiba mais sobre o Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4874,7 +4858,7 @@ Qual é o nosso users are saying apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4882,7 +4866,7 @@ Membros de todo o mundo estão usando Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4890,7 +4874,7 @@ Como é que Ghostfolio work? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4898,7 +4882,7 @@ Inscreva-se anonimamente* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4906,7 +4890,7 @@ * no e-mail address nor credit card required apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4914,7 +4898,7 @@ Adicione qualquer uma de suas transações históricas apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4922,7 +4906,7 @@ Obtenha insights valiosos sobre a composição do seu portfólio apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4930,7 +4914,7 @@ São you preparar? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4938,7 +4922,7 @@ Tenha uma visão completa das suas finanças pessoais em diversas plataformas. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -4946,7 +4930,7 @@ Comece em apenas 3 passos apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -5764,7 +5748,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5883,12 +5867,12 @@ 8 - + Reset Filters Redefinir filtros libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5923,12 +5907,12 @@ 411 - + Apply Filters Aplicar filtros libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6153,7 +6137,7 @@ Cadastre-se agora ou confira a conta de exemplo apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6329,7 +6313,7 @@ Código aberto apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6719,10 +6703,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,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 - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Nome - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Links rápidos @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Perfis de ativos - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Live Demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Gerenciar perfil de ativos apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index b867b8da8..b672a6f2c 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -349,6 +349,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -613,6 +617,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Historical Market Data @@ -1159,7 +1167,7 @@ Bu kullanıcıyı silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -1278,14 +1286,6 @@ 5 - - Get started - Haydi Başlayalım - - apps/client/src/app/components/header/header.component.html - 432 - - Sign in Giriş @@ -1299,7 +1299,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -1535,11 +1535,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1562,20 +1562,12 @@ 30 - - Sign in with Internet Identity - İnternet Kimliği (Internet Identity) ile Oturum Aç - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Google ile Oturum Aç apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -1583,7 +1575,7 @@ Oturumu açık tut apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -1739,7 +1731,7 @@ Rapor Veri Sorunu apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -2157,6 +2149,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Update account @@ -2549,14 +2545,34 @@ Get Started Başla + + 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Holdings @@ -2581,6 +2597,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -2662,32 +2682,12 @@ 11 - - Get Started - Başla - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - Monthly Active Users Aylık Aktif Kullanıcılar apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -2695,7 +2695,7 @@ GitHub’daki Beğeniler apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -2707,7 +2707,7 @@ Docker Hub’ta Çekmeler apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -2719,7 +2719,7 @@ Şurada görüldüğü gibi apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -2727,7 +2727,7 @@ varlıklarınızı koruyun. Kişisel yatırım stratejinizi geliştirin. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -2735,7 +2735,7 @@ Ghostfolio, takip edilmeden hisse senetleri, ETF’ler veya kripto paraları izlemek isteyen yoğun insanlara güç verir. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -2743,7 +2743,7 @@ 360° Görünüm apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -2751,7 +2751,7 @@ Kişisel finansınızın tam resmini birden fazla platformda edinin. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -2759,7 +2759,7 @@ Web3 Hazır apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -2767,7 +2767,7 @@ Ghostfolio’yu anonim olarak kullanın ve finansal verilerinize sahip çıkın. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -2775,7 +2775,7 @@ Güçlü bir topluluk aracılığıyla sürekli gelişmelerden faydalanın. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -2791,7 +2791,7 @@ Neden Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -2799,7 +2799,7 @@ Ghostfolio tam size göre, apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -2807,7 +2807,7 @@ Birden fazla platformda hisse senedi, ETF veya kripto para ticareti yapıyorsanız, apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -2815,7 +2815,7 @@ al ve tut stratejisi izliyorsanız, apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -2823,7 +2823,7 @@ Portföy bileşimine dair içgörüleri edinmek istiyorsanız, apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -2831,7 +2831,7 @@ Gizliliğe ve verilerinize sahip çıkmayı önemsiyorsanız apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -2839,7 +2839,7 @@ minimalizme ilgi duyuyorsanız apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -2847,7 +2847,7 @@ finansal kaynaklarınızı çeşitlendirmeye önem veriyorsanız apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -2855,7 +2855,7 @@ finansal özgürlük peşindeyseniz apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -2863,7 +2863,7 @@ elektronik tablo uygulamalarına hayır diyorsanız apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -2871,7 +2871,7 @@ bu listeyi hala okuyorsanız apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -2879,7 +2879,7 @@ Ghostfolio hakkında daha fazla bilgi edinin apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -2887,7 +2887,7 @@ Kullanıcılarımızın görüşleri apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -2895,7 +2895,7 @@ Dünyanın dört bir yanındaki kullanıcılar Ghostfolio Premium kullanıyorlar. apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -2903,7 +2903,7 @@ NasılGhostfolio çalışır? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -2911,7 +2911,7 @@ Sadece 3 adımda başlayın apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -2927,7 +2927,7 @@ Anonim olarak kaydolun* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -2935,7 +2935,7 @@ * e-posta adresi veya kredi kartı gerekmez apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -2943,7 +2943,7 @@ Herhangi bir geçmiş işleminizi ekleyin apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -2951,7 +2951,7 @@ Portföy bileşiminizle ilgili değerli bilgiler edinin apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -2959,7 +2959,7 @@ Hazır mısınız? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -3602,14 +3602,6 @@ 7 - - Holdings - Varlıklar - - libs/ui/src/lib/assistant/assistant.html - 110 - - Pricing Fiyatlandırma @@ -3831,7 +3823,7 @@ Ücretsiz. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -3898,20 +3890,12 @@ 101 - - Continue with Internet Identity - İnternet Kimliği ile Devam Et - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Google ile Devam Et apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -4904,7 +4888,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -5220,11 +5204,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5764,7 +5748,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5883,12 +5867,12 @@ 8 - + Reset Filters Filtreleri Sıfırla libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5923,12 +5907,12 @@ 411 - + Apply Filters Filtreleri Uygula libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6153,7 +6137,7 @@ Hemen katıl ya da örnek hesabı incele apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6329,7 +6313,7 @@ Açık Kaynak apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6719,10 +6703,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Güvenlik belirteci apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,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 - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - İsim - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Hızlı Bağlantılar @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Varlık Profilleri - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Canlı Demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 0305608c2..5677f32ca 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -42,7 +42,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -349,10 +349,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -426,14 +422,6 @@ 86 - - Holdings - Активи - - libs/ui/src/lib/assistant/assistant.html - 110 - - Cash Balances Баланс готівки @@ -501,6 +489,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -1519,11 +1511,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1595,7 +1587,7 @@ Ви дійсно хочете видалити цього користувача? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -1762,14 +1754,6 @@ 5 - - Get started - Почати - - apps/client/src/app/components/header/header.component.html - 432 - - Oops! Incorrect Security Token. Упс! Неправильний Секретний Токен. @@ -1887,7 +1871,7 @@ Повідомити про збій даних apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -2118,20 +2102,12 @@ 72 - - Sign in with Internet Identity - Увійти з Інтернет-Ідентичністю - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google Увійти з Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -2139,7 +2115,7 @@ Залишатися в системі apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -3177,6 +3153,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Oops, cash balance transfer has failed. @@ -3630,14 +3610,34 @@ 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Holdings @@ -3662,6 +3662,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -3775,32 +3779,12 @@ 11 - - Get Started - Почати - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - Monthly Active Users Щомісячні активні користувачі apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -3808,7 +3792,7 @@ Зірки на GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -3820,7 +3804,7 @@ Завантаження на Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -3832,7 +3816,7 @@ Як видно в apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -3840,7 +3824,7 @@ Захищайте свої активи. Вдосконалюйте власну інвестиційну стратегію. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -3848,7 +3832,7 @@ Ghostfolio допомагає зайнятим людям відстежувати акції, ETF або криптовалюти без ризику бути відстеженими. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -3856,7 +3840,7 @@ 360° огляд apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -3864,7 +3848,7 @@ Отримайте повну картину ваших особистих фінансів на різних платформах. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -3872,7 +3856,7 @@ Готовий до Web3 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -3880,7 +3864,7 @@ Використовуйте Ghostfolio анонімно та володійте своїми фінансовими даними. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -3888,7 +3872,7 @@ Отримуйте користь від постійних покращень завдяки сильній спільноті. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -3904,7 +3888,7 @@ Чому Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -3912,7 +3896,7 @@ Ghostfolio для вас, якщо ви... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -3920,7 +3904,7 @@ торгуєте акціями, ETF або криптовалютами на різних платформах apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -3928,7 +3912,7 @@ дотримуєтеся стратегії купівлі та утримання apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -3936,7 +3920,7 @@ вас цікавлять інсайти вашого складу портфеля apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -3944,7 +3928,7 @@ цінуєте конфіденційність і володіння даними apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -3952,7 +3936,7 @@ займаєтесь мінімалізмом apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -3960,7 +3944,7 @@ піклуєтесь про диверсифікацію ваших фінансових ресурсів apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -3968,7 +3952,7 @@ цікавитесь фінансовою незалежністю apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -3976,7 +3960,7 @@ кажете ні таблицям у apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -3984,7 +3968,7 @@ все ще читаєте цей список apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -3992,7 +3976,7 @@ Дізнайтеся більше про Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -4000,7 +3984,7 @@ Що говорять користувачі apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -4008,7 +3992,7 @@ Члени зі всього світу використовують Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -4016,7 +4000,7 @@ Як працює Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -4024,7 +4008,7 @@ Почніть всього за 3 кроки apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -4040,7 +4024,7 @@ Зареєструйтеся анонімно* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -4048,7 +4032,7 @@ * не потрібні електронна адреса та кредитна картка apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -4056,7 +4040,7 @@ Додайте будь-які з ваших історичних транзакцій apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -4064,7 +4048,7 @@ Отримуйте цінні інсайти вашого складу портфеля apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -4072,7 +4056,7 @@ Ви готові? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4080,7 +4064,7 @@ Приєднуйтесь зараз або перегляньте демонстраційний рахунок apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -4792,7 +4776,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5160,7 +5144,7 @@ Це безкоштовно. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -5231,20 +5215,12 @@ 281 - - Continue with Internet Identity - Продовжити з Інтернет-Ідентичністю - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google Продовжити з Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -5503,7 +5479,7 @@ Відкритий код apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6181,6 +6157,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + Date Range @@ -6190,20 +6170,20 @@ 170 - + Reset Filters Скинути фільтри libs/ui/src/lib/assistant/assistant.html - 205 + 204 - + Apply Filters Застосувати фільтри libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6683,7 +6663,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -7263,11 +7243,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -7395,7 +7375,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7415,11 +7395,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7515,7 +7495,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7527,7 +7507,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 - 233 + 237 @@ -7804,15 +7784,7 @@ 158 - - Name - Name - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links Quick Links @@ -7820,24 +7792,16 @@ 58 - - Asset Profiles - Asset Profiles - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo Live Demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8192,7 +8156,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 34502dbc9..d6f615dc8 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -372,6 +372,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -1221,7 +1225,7 @@ Do you really want to delete this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -1336,13 +1340,6 @@ 5 - - Get started - - apps/client/src/app/components/header/header.component.html - 432 - - Sign in @@ -1355,7 +1352,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -1570,11 +1567,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1597,25 +1594,18 @@ 30 - - Sign in with Internet Identity - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 Stay signed in apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -1754,7 +1744,7 @@ Report Data Glitch apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -2370,6 +2360,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Oops, cash balance transfer has failed. @@ -2751,14 +2745,34 @@ 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Holdings @@ -2782,6 +2796,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -2879,37 +2897,18 @@ 11 - - Get Started - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - Monthly Active Users apps/client/src/app/pages/landing/landing-page.html - 70 + 69 Stars on GitHub apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -2920,7 +2919,7 @@ Pulls on Docker Hub apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -2931,56 +2930,56 @@ As seen in apps/client/src/app/pages/landing/landing-page.html - 115 + 114 Protect your assets. Refine your personal investment strategy. apps/client/src/app/pages/landing/landing-page.html - 125 + 124 Ghostfolio empowers busy people to keep track of stocks, ETFs or cryptocurrencies without being tracked. apps/client/src/app/pages/landing/landing-page.html - 129 + 128 360° View apps/client/src/app/pages/landing/landing-page.html - 139 + 138 Get the full picture of your personal finances across multiple platforms. apps/client/src/app/pages/landing/landing-page.html - 142 + 141 Web3 Ready apps/client/src/app/pages/landing/landing-page.html - 150 + 149 Use Ghostfolio anonymously and own your financial data. apps/client/src/app/pages/landing/landing-page.html - 153 + 152 Benefit from continuous improvements through a strong community. apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -2994,112 +2993,112 @@ Why Ghostfolio? apps/client/src/app/pages/landing/landing-page.html - 171 + 170 Ghostfolio is for you if you are... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 trading stocks, ETFs or cryptocurrencies on multiple platforms apps/client/src/app/pages/landing/landing-page.html - 179 + 178 pursuing a buy & hold strategy apps/client/src/app/pages/landing/landing-page.html - 185 + 184 interested in getting insights of your portfolio composition apps/client/src/app/pages/landing/landing-page.html - 190 + 189 valuing privacy and data ownership apps/client/src/app/pages/landing/landing-page.html - 195 + 194 into minimalism apps/client/src/app/pages/landing/landing-page.html - 198 + 197 caring about diversifying your financial resources apps/client/src/app/pages/landing/landing-page.html - 202 + 201 interested in financial independence apps/client/src/app/pages/landing/landing-page.html - 206 + 205 saying no to spreadsheets in apps/client/src/app/pages/landing/landing-page.html - 210 + 209 still reading this list apps/client/src/app/pages/landing/landing-page.html - 213 + 212 Learn more about Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 218 + 217 What our users are saying apps/client/src/app/pages/landing/landing-page.html - 227 + 226 Members from around the globe are using Ghostfolio Premium apps/client/src/app/pages/landing/landing-page.html - 266 + 265 How does Ghostfolio work? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 Get started in only 3 steps apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -3113,35 +3112,35 @@ Sign up anonymously* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 * no e-mail address nor credit card required apps/client/src/app/pages/landing/landing-page.html - 293 + 292 Add any of your historical transactions apps/client/src/app/pages/landing/landing-page.html - 305 + 304 Get valuable insights of your portfolio composition apps/client/src/app/pages/landing/landing-page.html - 317 + 316 Are you ready? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -3780,13 +3779,6 @@ 7 - - Holdings - - libs/ui/src/lib/assistant/assistant.html - 110 - - Pricing @@ -3989,7 +3981,7 @@ It’s free. apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4031,18 +4023,11 @@ 281 - - Continue with Internet Identity - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -4482,6 +4467,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + 50-Day Trend @@ -4803,7 +4792,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -5133,11 +5122,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5247,7 +5236,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5369,11 +5358,11 @@ 42 - + Reset Filters libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5406,11 +5395,11 @@ 411 - + Apply Filters libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -5609,7 +5598,7 @@ Join now or check out the example account apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -5906,7 +5895,7 @@ Open Source apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6171,10 +6160,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -6727,7 +6712,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -6746,11 +6731,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -6834,7 +6819,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 - 233 + 237 @@ -6848,7 +6833,7 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7028,13 +7013,6 @@ 275 - - Name - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - Set up @@ -7077,29 +7055,22 @@ 158 - + Quick Links libs/ui/src/lib/assistant/assistant.html 58 - - Asset Profiles - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -7414,7 +7385,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 6d490fc0e..524e28259 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -398,6 +398,10 @@ libs/ui/src/lib/activities-table/activities-table.component.html 135 + + libs/ui/src/lib/benchmark/benchmark.component.html + 12 + libs/ui/src/lib/holdings-table/holdings-table.component.html 28 @@ -1304,7 +1308,7 @@ 您真的要删除该用户吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 207 + 211 @@ -1431,14 +1435,6 @@ 5 - - Get started - 开始使用 - - apps/client/src/app/components/header/header.component.html - 432 - - Sign in 登入 @@ -1452,7 +1448,7 @@ apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 71 + 60 libs/common/src/lib/routes/routes.ts @@ -1688,11 +1684,11 @@ apps/client/src/app/pages/landing/landing-page.html - 48 + 47 apps/client/src/app/pages/landing/landing-page.html - 350 + 348 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -1715,20 +1711,12 @@ 30 - - Sign in with Internet Identity - 使用互联网身份登录 - - apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 38 - - Sign in with Google 使用 Google 登录 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 48 + 37 @@ -1736,7 +1724,7 @@ 保持登录 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html - 59 + 48 @@ -1892,7 +1880,7 @@ 报告数据故障 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 450 + 451 @@ -2566,6 +2554,10 @@ libs/common/src/lib/routes/routes.ts 69 + + libs/ui/src/lib/assistant/assistant.html + 84 + Oops, cash balance transfer has failed. @@ -2978,14 +2970,34 @@ 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 + 377 + apps/client/src/app/pages/public/public-page.html 242 + + apps/client/src/app/pages/resources/personal-finance-tools/product-page.html + 334 + Holdings @@ -3010,6 +3022,10 @@ libs/common/src/lib/routes/routes.ts 167 + + libs/ui/src/lib/assistant/assistant.html + 110 + Summary @@ -3115,32 +3131,12 @@ 11 - - Get Started - 开始使用 - - apps/client/src/app/pages/landing/landing-page.html - 42 - - - apps/client/src/app/pages/landing/landing-page.html - 346 - - - apps/client/src/app/pages/pricing/pricing-page.html - 378 - - - apps/client/src/app/pages/resources/personal-finance-tools/product-page.html - 334 - - Monthly Active Users 每月活跃用户数 apps/client/src/app/pages/landing/landing-page.html - 70 + 69 @@ -3148,7 +3144,7 @@ GitHub 上的星星 apps/client/src/app/pages/landing/landing-page.html - 88 + 87 apps/client/src/app/pages/open/open-page.html @@ -3160,7 +3156,7 @@ Docker Hub 拉取次数 apps/client/src/app/pages/landing/landing-page.html - 106 + 105 apps/client/src/app/pages/open/open-page.html @@ -3172,7 +3168,7 @@ 如图所示 apps/client/src/app/pages/landing/landing-page.html - 115 + 114 @@ -3180,7 +3176,7 @@ 保护你的资产。完善你的个人投资策略 apps/client/src/app/pages/landing/landing-page.html - 125 + 124 @@ -3188,7 +3184,7 @@ Ghostfolio 使忙碌的人们能够在不被追踪的情况下跟踪股票、ETF 或加密货币。 apps/client/src/app/pages/landing/landing-page.html - 129 + 128 @@ -3196,7 +3192,7 @@ 360° 视角 apps/client/src/app/pages/landing/landing-page.html - 139 + 138 @@ -3204,7 +3200,7 @@ 跨多个平台全面了解您的个人财务状况。 apps/client/src/app/pages/landing/landing-page.html - 142 + 141 @@ -3212,7 +3208,7 @@ Web3 就绪 apps/client/src/app/pages/landing/landing-page.html - 150 + 149 @@ -3220,7 +3216,7 @@ 匿名使用 Ghostfolio 并拥有您的财务数据。 apps/client/src/app/pages/landing/landing-page.html - 153 + 152 @@ -3228,7 +3224,7 @@ 通过强大的社区不断改进,从中受益。 apps/client/src/app/pages/landing/landing-page.html - 163 + 162 @@ -3244,7 +3240,7 @@ 为什么使用Ghostfolio apps/client/src/app/pages/landing/landing-page.html - 171 + 170 @@ -3252,7 +3248,7 @@ 如果您符合以下条件,那么 Ghostfolio 适合您... apps/client/src/app/pages/landing/landing-page.html - 173 + 172 @@ -3260,7 +3256,7 @@ 在多个平台上交易股票、ETF 或加密货币 apps/client/src/app/pages/landing/landing-page.html - 179 + 178 @@ -3268,7 +3264,7 @@ 采取买入并持有策略 apps/client/src/app/pages/landing/landing-page.html - 185 + 184 @@ -3276,7 +3272,7 @@ 有兴趣深入了解您的投资组合构成 apps/client/src/app/pages/landing/landing-page.html - 190 + 189 @@ -3284,7 +3280,7 @@ 重视隐私和数据所有权 apps/client/src/app/pages/landing/landing-page.html - 195 + 194 @@ -3292,7 +3288,7 @@ 进入极简主义 apps/client/src/app/pages/landing/landing-page.html - 198 + 197 @@ -3300,7 +3296,7 @@ 关心您的财务资源多元化 apps/client/src/app/pages/landing/landing-page.html - 202 + 201 @@ -3308,7 +3304,7 @@ 对财务独立感兴趣 apps/client/src/app/pages/landing/landing-page.html - 206 + 205 @@ -3316,7 +3312,7 @@ 年对电子表格说不 apps/client/src/app/pages/landing/landing-page.html - 210 + 209 @@ -3324,7 +3320,7 @@ 仍在阅读此列表 apps/client/src/app/pages/landing/landing-page.html - 213 + 212 @@ -3332,7 +3328,7 @@ 了解有关 Ghostfolio 的更多信息 apps/client/src/app/pages/landing/landing-page.html - 218 + 217 @@ -3340,7 +3336,7 @@ 听听我们的用户怎么说 apps/client/src/app/pages/landing/landing-page.html - 227 + 226 @@ -3348,7 +3344,7 @@ 来自世界各地的会员正在使用Ghostfolio 高级版 apps/client/src/app/pages/landing/landing-page.html - 266 + 265 @@ -3356,7 +3352,7 @@ Ghostfolio 如何工作? apps/client/src/app/pages/landing/landing-page.html - 283 + 282 @@ -3364,7 +3360,7 @@ 只需 3 步即可开始 apps/client/src/app/pages/landing/landing-page.html - 285 + 284 @@ -3380,7 +3376,7 @@ 匿名注册* apps/client/src/app/pages/landing/landing-page.html - 291 + 290 @@ -3388,7 +3384,7 @@ * 无需电子邮件地址或信用卡 apps/client/src/app/pages/landing/landing-page.html - 293 + 292 @@ -3396,7 +3392,7 @@ 添加您的任何历史交易 apps/client/src/app/pages/landing/landing-page.html - 305 + 304 @@ -3404,7 +3400,7 @@ 获取有关您的投资组合构成的宝贵见解 apps/client/src/app/pages/landing/landing-page.html - 317 + 316 @@ -3412,7 +3408,7 @@ 准备好了吗? apps/client/src/app/pages/landing/landing-page.html - 331 + 330 @@ -4123,14 +4119,6 @@ 7 - - Holdings - 持仓 - - libs/ui/src/lib/assistant/assistant.html - 110 - - Pricing 价格 @@ -4352,7 +4340,7 @@ 免费。 apps/client/src/app/pages/pricing/pricing-page.html - 380 + 379 @@ -4399,20 +4387,12 @@ 281 - - Continue with Internet Identity - 继续互联网身份 - - apps/client/src/app/pages/register/register-page.html - 42 - - Continue with Google 继续使用谷歌 apps/client/src/app/pages/register/register-page.html - 53 + 39 @@ -4899,6 +4879,10 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html 106 + + libs/ui/src/lib/assistant/assistant.html + 140 + 50-Day Trend @@ -5253,7 +5237,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 412 + 413 @@ -5621,11 +5605,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 414 + 415 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 427 + 428 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5749,7 +5733,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 441 + 442 @@ -5884,12 +5868,12 @@ 42 - + Reset Filters 重置过滤器 libs/ui/src/lib/assistant/assistant.html - 205 + 204 @@ -5924,12 +5908,12 @@ 411 - + Apply Filters 应用过滤器 libs/ui/src/lib/assistant/assistant.html - 219 + 217 @@ -6154,7 +6138,7 @@ 立即加入 或查看示例账户 apps/client/src/app/pages/landing/landing-page.html - 334 + 333 @@ -6330,7 +6314,7 @@ 开源 apps/client/src/app/pages/landing/landing-page.html - 160 + 159 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -6720,10 +6704,6 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 51 - - libs/ui/src/lib/assistant/assistant.html - 84 - Copy link to clipboard @@ -7396,7 +7376,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 @@ -7416,11 +7396,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 367 + 368 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 380 + 381 @@ -7516,7 +7496,7 @@ 安全令牌 apps/client/src/app/components/admin-users/admin-users.component.ts - 228 + 232 apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -7528,7 +7508,7 @@ 您确定要为此用户生成新的安全令牌吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 233 + 237 @@ -7805,15 +7785,7 @@ 158 - - Name - 名称 - - libs/ui/src/lib/benchmark/benchmark.component.html - 12 - - - + Quick Links 快速链接 @@ -7821,24 +7793,16 @@ 58 - - Asset Profiles - 资产概况 - - libs/ui/src/lib/assistant/assistant.html - 140 - - Live Demo 现场演示 apps/client/src/app/pages/landing/landing-page.html - 49 + 48 apps/client/src/app/pages/landing/landing-page.html - 351 + 349 libs/common/src/lib/routes/routes.ts @@ -8193,7 +8157,7 @@ 管理资产概况 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 465 + 466 From a1920fedd5fd52ce1cff78366bef770701b178f2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 5 Nov 2025 11:54:22 +0100 Subject: [PATCH 074/146] Feature/improve usability for benchmark and markets management in asset profile dialog (#5911) * Improve usability for benchmark and markets management * Update changelog --- CHANGELOG.md | 2 ++ .../asset-profile-dialog/asset-profile-dialog.html | 8 ++++++-- .../app/pages/faq/self-hosting/self-hosting-page.html | 9 ++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ff76b0c2..bca1a8de9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page +- Improved the _Self-Hosting_ section content for the _Markets_ concept on the Frequently Asked Questions (FAQ) page - Changed the build executor of the client from `@nx/angular:webpack-browser` to `@nx/angular:browser-esbuild` - Improved the language localization for German (`de`) diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index b2c063684..3d855e6e0 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -352,7 +352,6 @@
Benchmark + Include in +   + Benchmark + / + Markets +
diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html index bc468fe96..f44759124 100644 --- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html +++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html @@ -184,7 +184,9 @@
  • Open the Admin Control panel
  • Navigate to the Market Data section
  • Choose an asset profile
  • -
  • In the dialog, check the Benchmark box
  • +
  • + In the dialog, check the Include in Benchmark / Markets box +
  • @@ -212,12 +214,13 @@ How do I set up Markets? -

    The Markets list is derived from your Benchmarks.

    1. Open the Admin Control panel
    2. Navigate to the Market Data section
    3. Choose an asset profile
    4. -
    5. In the dialog, check the Benchmark box
    6. +
    7. + In the dialog, check the Include in Benchmark / Markets box +

    Please note: Data is cached, meaning changes may take a few minutes From 58d9235b8a195f0e05152f9c61c2d8a49f14e134 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:25:41 +0100 Subject: [PATCH 075/146] Feature/update locales (#5916) * Update locales * Update translation --------- Co-authored-by: github-actions[bot] Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- apps/client/src/locales/messages.ca.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.de.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.es.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.fr.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.it.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.nl.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.pl.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.pt.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.tr.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.uk.xlf | 50 +++++++++++++++---------- apps/client/src/locales/messages.xlf | 49 ++++++++++++++---------- apps/client/src/locales/messages.zh.xlf | 50 +++++++++++++++---------- 12 files changed, 371 insertions(+), 228 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 361c4ec31..7513eedaf 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -687,7 +687,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -1071,7 +1071,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1091,7 +1091,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1103,7 +1103,7 @@ Mapatge de Símbols apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -1119,7 +1119,7 @@ Configuració del Proveïdor de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -1127,7 +1127,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -1135,11 +1135,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1155,7 +1155,7 @@ Notes apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1303,7 +1303,7 @@ Recollida de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1563,7 +1563,7 @@ Punt de Referència apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2498,6 +2498,14 @@ 280 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Ups! Hi ha hagut un error en configurar l’autenticació biomètrica. @@ -2551,7 +2559,7 @@ Localització apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3390,6 +3398,10 @@ Markets Mercats + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -6589,7 +6601,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6641,7 +6653,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7183,7 +7195,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 9a0ebd159..a3583df02 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -394,7 +394,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -962,7 +962,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -982,7 +982,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1262,7 +1262,7 @@ Lokalität apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1664,6 +1664,10 @@ Markets Märkte + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -1966,7 +1970,7 @@ Kommentar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2606,7 +2610,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -3038,7 +3042,7 @@ Symbol Zuordnung apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -3774,11 +3778,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -4142,7 +4146,7 @@ Scraper Konfiguration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -5660,7 +5664,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5944,7 +5948,7 @@ Finanzmarktdaten synchronisieren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6164,6 +6168,14 @@ 333 + + Include in + Berücksichtigen in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Ups! Beim Einrichten der biometrischen Authentifizierung ist ein Fehler aufgetreten. @@ -6613,7 +6625,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6665,7 +6677,7 @@ Schliessen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7207,7 +7219,7 @@ Speichern apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7331,7 +7343,7 @@ Standardmarktpreis apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7339,7 +7351,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7347,7 +7359,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7355,7 +7367,7 @@ HTTP Request-Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 07e4e855d..62f437994 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -395,7 +395,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -947,7 +947,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -967,7 +967,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1247,7 +1247,7 @@ Ubicación apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1649,6 +1649,10 @@ Markets Mercados + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -1951,7 +1955,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2583,7 +2587,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -3023,7 +3027,7 @@ Mapeo de símbolos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -3751,11 +3755,11 @@ ¿La URL? apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -4119,7 +4123,7 @@ Configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -5637,7 +5641,7 @@ Prueba apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5921,7 +5925,7 @@ Recopilación de datos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6141,6 +6145,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. ¡Ups! Hubo un error al configurar la autenticación biométrica. @@ -6590,7 +6602,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6642,7 +6654,7 @@ Cerca apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7184,7 +7196,7 @@ Ahorrar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7308,7 +7320,7 @@ Precio de mercado por defecto apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7316,7 +7328,7 @@ Modo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7324,7 +7336,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7332,7 +7344,7 @@ Encabezados de solicitud HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index f51609582..560859d05 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -450,7 +450,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -642,7 +642,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -662,7 +662,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -674,7 +674,7 @@ Équivalence de Symboles apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -682,7 +682,7 @@ Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -914,7 +914,7 @@ Référence apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1534,7 +1534,7 @@ Paramètres régionaux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1928,6 +1928,10 @@ Markets Marchés + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -3750,11 +3754,11 @@ Lien apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -4118,7 +4122,7 @@ Configuration du Scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -5636,7 +5640,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5920,7 +5924,7 @@ Collecter les données apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6140,6 +6144,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Oops! Une erreur s’est produite lors de la configuration de l’authentification biométrique. @@ -6589,7 +6601,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6641,7 +6653,7 @@ Fermer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7183,7 +7195,7 @@ Sauvegarder apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Prix du marché par défaut apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Selecteur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ En-têtes de requête HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 5b4058606..076c02068 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -395,7 +395,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -947,7 +947,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -967,7 +967,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1247,7 +1247,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1649,6 +1649,10 @@ Markets Mercati + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -1951,7 +1955,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2583,7 +2587,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -3023,7 +3027,7 @@ Mappatura dei simboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -3751,11 +3755,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -4119,7 +4123,7 @@ Configurazione dello scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -5637,7 +5641,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5921,7 +5925,7 @@ Raccolta Dati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6141,6 +6145,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Ops! C’è stato un errore impostando l’autenticazione biometrica. @@ -6590,7 +6602,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6642,7 +6654,7 @@ Chiudi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7184,7 +7196,7 @@ Salva apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7308,7 +7320,7 @@ Prezzo di mercato predefinito apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7316,7 +7328,7 @@ Modalità apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7324,7 +7336,7 @@ Selettore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7332,7 +7344,7 @@ Intestazioni della richiesta HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 6a1f871a6..4a17736b4 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -394,7 +394,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -946,7 +946,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -966,7 +966,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1246,7 +1246,7 @@ Locatie apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1648,6 +1648,10 @@ Markets Markten + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -1950,7 +1954,7 @@ Opmerking apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2582,7 +2586,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -3022,7 +3026,7 @@ Symbool toewijzen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -3750,11 +3754,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -4118,7 +4122,7 @@ Scraper instellingen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -5636,7 +5640,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5920,7 +5924,7 @@ Data Verzamelen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6140,6 +6144,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Oeps! Er is een fout opgetreden met het instellen van de biometrische authenticatie. @@ -6589,7 +6601,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6641,7 +6653,7 @@ Sluiten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7183,7 +7195,7 @@ Opslaan apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Standaard Marktprijs apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Kiezer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ HTTP Verzoek Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index a52b15599..321cfbecd 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -591,7 +591,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -919,7 +919,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -939,7 +939,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -951,7 +951,7 @@ Mapowanie Symboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -967,7 +967,7 @@ Konfiguracja Scrapera apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -975,7 +975,7 @@ Notatka apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1191,11 +1191,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1391,7 +1391,7 @@ Poziom Odniesienia (Benchmark) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2247,7 +2247,7 @@ Ustawienia Regionalne apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3033,6 +3033,10 @@ Markets Rynki + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -5636,7 +5640,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5920,7 +5924,7 @@ Gromadzenie Danych apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6140,6 +6144,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Ups! Wystąpił błąd podczas konfigurowania uwierzytelniania biometrycznego. @@ -6589,7 +6601,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6641,7 +6653,7 @@ Zamknij apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7183,7 +7195,7 @@ Zapisz apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Domyślna cena rynkowa apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Tryb apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ Nagłówki żądań HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 3867d6fbe..dc8804544 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -450,7 +450,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -786,7 +786,7 @@ Referência apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1166,7 +1166,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1186,7 +1186,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1530,7 +1530,7 @@ Localidade apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1900,6 +1900,10 @@ Markets Mercados + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -2062,7 +2066,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -3030,7 +3034,7 @@ Mapeamento de Símbolo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -3750,11 +3754,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -4118,7 +4122,7 @@ Configuração do raspador apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -5636,7 +5640,7 @@ Teste apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5920,7 +5924,7 @@ Coleta de dados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6140,6 +6144,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Ops! Ocorreu um erro ao configurar a autenticação biométrica. @@ -6589,7 +6601,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6641,7 +6653,7 @@ Fechar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7183,7 +7195,7 @@ Guardar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Preço de mercado padrão apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index b672a6f2c..235f670a3 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -631,7 +631,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -851,7 +851,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -871,7 +871,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -883,7 +883,7 @@ Sembol Eşleştirme apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -899,7 +899,7 @@ Veri Toplayıcı Yapılandırması apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -907,7 +907,7 @@ Not apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1107,11 +1107,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1259,7 +1259,7 @@ Karşılaştırma Ölçütü apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2617,6 +2617,10 @@ Markets Piyasalar + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -4380,7 +4384,7 @@ Yerel Ayarlar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -5636,7 +5640,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5920,7 +5924,7 @@ Veri Toplama apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6140,6 +6144,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Oops! Biyometrik kimlik doğrulama ayarlanırken bir hata oluştu. @@ -6589,7 +6601,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6641,7 +6653,7 @@ Kapat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7183,7 +7195,7 @@ Kaydet apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Varsayılan Piyasa Fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Mod apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Seçici apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ HTTP İstek Başlıkları apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 5677f32ca..c1f2c7bce 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -711,7 +711,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -1059,7 +1059,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1079,7 +1079,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1091,7 +1091,7 @@ Зіставлення символів apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -1107,7 +1107,7 @@ Конфігурація скребка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -1115,7 +1115,7 @@ Тест apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -1123,11 +1123,11 @@ URL apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1143,7 +1143,7 @@ Примітка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1299,7 +1299,7 @@ Збір даних apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1683,7 +1683,7 @@ Порівняльний показник apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2239,7 +2239,7 @@ Зберегти apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2798,6 +2798,14 @@ 280 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. Упс! Виникла помилка під час налаштування біометричної автентифікації. @@ -2851,7 +2859,7 @@ Локалізація apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3682,6 +3690,10 @@ Markets Ринки + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -6467,7 +6479,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6527,7 +6539,7 @@ Закрити apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7307,7 +7319,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7315,7 +7327,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7323,7 +7335,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7331,7 +7343,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index d6f615dc8..3a6ce2f09 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -566,7 +566,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -885,7 +885,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -904,7 +904,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -915,7 +915,7 @@ Symbol Mapping apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -929,14 +929,14 @@ Scraper Configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1128,11 +1128,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1308,7 +1308,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2087,7 +2087,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2814,6 +2814,10 @@ Markets + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -5158,7 +5162,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5429,7 +5433,7 @@ Data Gathering apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -5601,6 +5605,13 @@ 333 + + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. @@ -5966,7 +5977,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6045,7 +6056,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6538,7 +6549,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6631,7 +6642,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -6645,14 +6656,14 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -6673,7 +6684,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 524e28259..1595ea726 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -600,7 +600,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 563 + 567 @@ -928,7 +928,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 511 + 515 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -948,7 +948,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 522 + 526 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -960,7 +960,7 @@ 代码映射 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 375 + 379 @@ -976,7 +976,7 @@ 刮削配置 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 400 + 404 @@ -984,7 +984,7 @@ 笔记 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 547 + 551 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1200,11 +1200,11 @@ 网址 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 482 + 486 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 538 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1400,7 +1400,7 @@ 基准 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 369 + 371 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2256,7 +2256,7 @@ 语言环境 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 437 + 441 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3042,6 +3042,10 @@ Markets 市场 + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 373 + apps/client/src/app/components/footer/footer.component.html 11 @@ -5645,7 +5649,7 @@ 测试 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 500 + 504 @@ -5946,7 +5950,7 @@ 数据收集 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 593 + 597 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6141,6 +6145,14 @@ 333 + + Include in + Include in + + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html + 369 + + Oops! There was an error setting up biometric authentication. 哎呀!设置生物识别认证时发生错误。 @@ -6590,7 +6602,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 598 + 602 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6642,7 +6654,7 @@ 关闭 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 600 + 604 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7184,7 +7196,7 @@ 保存 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 609 + 613 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7308,7 +7320,7 @@ 默认市场价格 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 409 + 413 @@ -7316,7 +7328,7 @@ 模式 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 450 + 454 @@ -7324,7 +7336,7 @@ 选择器 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 466 + 470 @@ -7332,7 +7344,7 @@ HTTP 请求标头 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 422 + 426 From f0ea31279e84ef4cd1845f88dab91bba414dbf35 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:58:55 +0100 Subject: [PATCH 076/146] Bugfix/header alignment in admin platform and tag tables (#5908) * Fix header alignment * Update changelog --- CHANGELOG.md | 2 ++ .../app/components/admin-platform/admin-platform.component.html | 2 +- .../src/app/components/admin-tag/admin-tag.component.html | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bca1a8de9..a73619a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the style of the safe withdrawal rate selector in the _FIRE_ section (experimental) +- Improved the table headers’ alignment in the platform management of the admin control panel +- Improved the table headers’ alignment in the tag management of the admin control panel ## 2.214.0 - 2025-11-01 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 9e38d5de7..e71dcf17b 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 @@ -45,7 +45,7 @@ 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 5979d2778..8b1b510d7 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 @@ -38,7 +38,7 @@ From 45b21cada92eb26c56056470ba758886c95d68c6 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:21:03 -0600 Subject: [PATCH 077/146] Task/migrate app component to standalone (#5906) * Migrate app component to standalone * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/app.component.ts | 18 ++-- apps/client/src/app/app.module.ts | 83 ----------------- .../{app-routing.module.ts => app.routes.ts} | 30 +----- apps/client/src/main.ts | 91 +++++++++++++++++-- 5 files changed, 97 insertions(+), 126 deletions(-) delete mode 100644 apps/client/src/app/app.module.ts rename apps/client/src/app/{app-routing.module.ts => app.routes.ts} (81%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a73619a2f..6bbaba5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page - Improved the _Self-Hosting_ section content for the _Markets_ concept on the Frequently Asked Questions (FAQ) page - 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`) ### Fixed diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 5ecb7bf8b..b70850016 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -1,5 +1,3 @@ -import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component'; -import { HoldingDetailDialogParams } from '@ghostfolio/client/components/holding-detail-dialog/interfaces/interfaces'; import { getCssVariable } from '@ghostfolio/common/helper'; import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -22,7 +20,9 @@ import { ActivatedRoute, NavigationEnd, PRIMARY_OUTLET, - Router + Router, + RouterLink, + RouterOutlet } from '@angular/router'; import { DataSource } from '@prisma/client'; import { addIcons } from 'ionicons'; @@ -31,6 +31,10 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; +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'; @@ -38,13 +42,13 @@ import { TokenStorageService } from './services/token-storage.service'; import { UserService } from './services/user/user.service'; @Component({ - selector: 'gf-root', changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './app.component.html', + imports: [GfFooterComponent, GfHeaderComponent, RouterLink, RouterOutlet], + selector: 'gf-root', styleUrls: ['./app.component.scss'], - standalone: false + templateUrl: './app.component.html' }) -export class AppComponent implements OnDestroy, OnInit { +export class GfAppComponent implements OnDestroy, OnInit { @HostBinding('class.has-info-message') get getHasMessage() { return this.hasInfoMessage; } diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts deleted file mode 100644 index 63de8fca7..000000000 --- a/apps/client/src/app/app.module.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Platform } from '@angular/cdk/platform'; -import { - provideHttpClient, - withInterceptorsFromDi -} from '@angular/common/http'; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatChipsModule } from '@angular/material/chips'; -import { - DateAdapter, - MAT_DATE_FORMATS, - MAT_DATE_LOCALE, - MatNativeDateModule -} from '@angular/material/core'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ServiceWorkerModule } from '@angular/service-worker'; -import { provideIonicAngular } from '@ionic/angular/standalone'; -import { provideMarkdown } from 'ngx-markdown'; -import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader'; -import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; - -import { environment } from '../environments/environment'; -import { CustomDateAdapter } from './adapter/custom-date-adapter'; -import { DateFormats } from './adapter/date-formats'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { GfFooterComponent } from './components/footer/footer.component'; -import { GfHeaderComponent } from './components/header/header.component'; -import { authInterceptorProviders } from './core/auth.interceptor'; -import { httpResponseInterceptorProviders } from './core/http-response.interceptor'; -import { LanguageService } from './core/language.service'; -import { GfNotificationModule } from './core/notification/notification.module'; - -export function NgxStripeFactory(): string { - return environment.stripePublicKey; -} - -@NgModule({ - bootstrap: [AppComponent], - declarations: [AppComponent], - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - GfFooterComponent, - GfHeaderComponent, - GfNotificationModule, - MatAutocompleteModule, - MatChipsModule, - MatNativeDateModule, - MatSnackBarModule, - MatTooltipModule, - NgxStripeModule.forRoot(environment.stripePublicKey), - ServiceWorkerModule.register('ngsw-worker.js', { - enabled: environment.production, - registrationStrategy: 'registerImmediately' - }) - ], - providers: [ - authInterceptorProviders, - httpResponseInterceptorProviders, - LanguageService, - provideHttpClient(withInterceptorsFromDi()), - provideIonicAngular(), - provideMarkdown(), - provideNgxSkeletonLoader(), - { - provide: DateAdapter, - useClass: CustomDateAdapter, - deps: [LanguageService, MAT_DATE_LOCALE, Platform] - }, - { provide: MAT_DATE_FORMATS, useValue: DateFormats }, - { - provide: STRIPE_PUBLISHABLE_KEY, - useFactory: NgxStripeFactory - } - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] -}) -export class AppModule {} diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app.routes.ts similarity index 81% rename from apps/client/src/app/app-routing.module.ts rename to apps/client/src/app/app.routes.ts index fb045a174..9588cee68 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app.routes.ts @@ -1,13 +1,10 @@ -import { publicRoutes, internalRoutes } from '@ghostfolio/common/routes/routes'; +import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; -import { NgModule } from '@angular/core'; -import { RouterModule, Routes, TitleStrategy } from '@angular/router'; +import { Routes } from '@angular/router'; import { AuthGuard } from './core/auth.guard'; -import { ModulePreloadService } from './core/module-preload.service'; -import { PageTitleStrategy } from './services/page-title.strategy'; -const routes: Routes = [ +export const routes: Routes = [ { path: publicRoutes.about.path, loadChildren: () => @@ -147,24 +144,3 @@ const routes: Routes = [ pathMatch: 'full' } ]; - -@NgModule({ - imports: [ - RouterModule.forRoot( - routes, - // Preload all lazy loaded modules with the attribute preload === true - { - anchorScrolling: 'enabled', - // enableTracing: true, // <-- debugging purposes only - preloadingStrategy: ModulePreloadService, - scrollPositionRestoration: 'top' - } - ) - ], - providers: [ - ModulePreloadService, - { provide: TitleStrategy, useClass: PageTitleStrategy } - ], - exports: [RouterModule] -}) -export class AppRoutingModule {} diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index 96d6c0582..fc8a9ef7a 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -2,11 +2,39 @@ import { locale } from '@ghostfolio/common/config'; import { InfoResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; -import { enableProdMode } from '@angular/core'; -import { LOCALE_ID } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { Platform } from '@angular/cdk/platform'; +import { + provideHttpClient, + withInterceptorsFromDi +} from '@angular/common/http'; +import { enableProdMode, importProvidersFrom, LOCALE_ID } from '@angular/core'; +import { + DateAdapter, + MAT_DATE_FORMATS, + MAT_DATE_LOCALE, + MatNativeDateModule +} from '@angular/material/core'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { RouterModule, TitleStrategy } from '@angular/router'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { provideIonicAngular } from '@ionic/angular/standalone'; +import { provideMarkdown } from 'ngx-markdown'; +import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader'; +import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; -import { AppModule } from './app/app.module'; +import { CustomDateAdapter } from './app/adapter/custom-date-adapter'; +import { DateFormats } from './app/adapter/date-formats'; +import { GfAppComponent } from './app/app.component'; +import { routes } from './app/app.routes'; +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'; (async () => { @@ -29,9 +57,54 @@ import { environment } from './environments/environment'; enableProdMode(); } - platformBrowserDynamic() - .bootstrapModule(AppModule, { - providers: [{ provide: LOCALE_ID, useValue: locale }] - }) - .catch((error) => console.error(error)); + await bootstrapApplication(GfAppComponent, { + providers: [ + authInterceptorProviders, + httpResponseInterceptorProviders, + importProvidersFrom( + GfNotificationModule, + MatNativeDateModule, + MatSnackBarModule, + MatTooltipModule, + NgxStripeModule.forRoot(environment.stripePublicKey), + RouterModule.forRoot(routes, { + anchorScrolling: 'enabled', + preloadingStrategy: ModulePreloadService, + scrollPositionRestoration: 'top' + }), + ServiceWorkerModule.register('ngsw-worker.js', { + enabled: environment.production, + registrationStrategy: 'registerImmediately' + }) + ), + LanguageService, + ModulePreloadService, + provideAnimations(), + provideHttpClient(withInterceptorsFromDi()), + provideIonicAngular(), + provideMarkdown(), + provideNgxSkeletonLoader(), + { + deps: [LanguageService, MAT_DATE_LOCALE, Platform], + provide: DateAdapter, + useClass: CustomDateAdapter + }, + { + provide: LOCALE_ID, + useValue: locale + }, + { + provide: MAT_DATE_FORMATS, + useValue: DateFormats + }, + { + provide: STRIPE_PUBLISHABLE_KEY, + useFactory: () => environment.stripePublicKey + }, + { + provide: TitleStrategy, + useClass: PageTitleStrategy + } + ] + }); })(); From 1ae3519d7f89223568cd725a3fab44c51e061d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Mart=C3=ADn?= Date: Wed, 5 Nov 2025 20:26:59 +0100 Subject: [PATCH 078/146] Bugfix/assign admin role to first user signing up (#5914) * Assign admin role to first user signing up * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/user/user.controller.ts | 6 +----- apps/api/src/app/user/user.service.ts | 20 ++++++++++++++------ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bbaba5ac..59142bbbd 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 style of the safe withdrawal rate selector in the _FIRE_ section (experimental) +- Assigned the `ADMIN` role to the first user signing up via a social login provider if no administrator existed - Improved the table headers’ alignment in the platform management of the admin control panel - Improved the table headers’ alignment in the tag management of the admin control panel diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index e545fd335..8704662f7 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -126,11 +126,7 @@ export class UserController { ); } - const hasAdmin = await this.userService.hasAdmin(); - - const { accessToken, id, role } = await this.userService.createUser({ - data: { role: hasAdmin ? 'USER' : 'ADMIN' } - }); + const { accessToken, id, role } = await this.userService.createUser(); return { accessToken, diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index f797270ff..65ce92cb2 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -526,15 +526,23 @@ export class UserService { }); } - public async createUser({ - data - }: { - data: Prisma.UserCreateInput; - }): Promise { - if (!data?.provider) { + public async createUser( + { + data + }: { + data: Prisma.UserCreateInput; + } = { data: {} } + ): Promise { + if (!data.provider) { data.provider = 'ANONYMOUS'; } + if (!data.role) { + const hasAdmin = await this.hasAdmin(); + + data.role = hasAdmin ? 'USER' : 'ADMIN'; + } + const user = await this.prismaService.user.create({ data: { ...data, From 21dc25119dbd55812da114c755ed045f8b2b0a5e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:03:42 +0100 Subject: [PATCH 079/146] Release 2.215.0-beta.1 (#5918) --- 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 59142bbbd..801b33652 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.215.0-beta.1 - 2025-11-05 ### Changed diff --git a/package-lock.json b/package-lock.json index 6429912bb..095b9f7f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.214.0", + "version": "2.215.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.214.0", + "version": "2.215.0-beta.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 7648cee02..ea8646565 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.214.0", + "version": "2.215.0-beta.1", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 697ecfe9bd5f7be000e1aadf3013a85c821ea27f Mon Sep 17 00:00:00 2001 From: Arghya Das Date: Thu, 6 Nov 2025 01:54:52 +0530 Subject: [PATCH 080/146] Feature/add endpoint to get user by id (#5910) * Add endpoint to get user by id * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 4 ++ apps/api/src/app/admin/admin.controller.ts | 8 ++++ apps/api/src/app/admin/admin.service.ts | 38 +++++++++++++------ .../lib/interfaces/admin-user.interface.ts | 13 +++++++ libs/common/src/lib/interfaces/index.ts | 4 ++ .../admin-user-response.interface.ts | 3 ++ .../admin-users-response.interface.ts | 14 +------ 7 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 libs/common/src/lib/interfaces/admin-user.interface.ts create mode 100644 libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 801b33652..79ff17256 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 ## 2.215.0-beta.1 - 2025-11-05 +### Added + +- Added the endpoint `GET /api/v1/admin/user/:id` + ### Changed - Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 2419b0a7d..7ed7f364b 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -17,6 +17,7 @@ import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { AdminData, AdminMarketData, + AdminUserResponse, AdminUsersResponse, EnhancedSymbolProfile, ScraperConfiguration @@ -321,4 +322,11 @@ export class AdminController { take: isNaN(take) ? undefined : take }); } + + @Get('user/:id') + @HasPermission(permissions.accessAdminControl) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getUser(@Param('id') id: string): Promise { + return this.adminService.getUser(id); + } } diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 683e72cb8..6b29b141a 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -23,6 +23,7 @@ import { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + AdminUserResponse, AdminUsersResponse, AssetProfileIdentifier, EnhancedSymbolProfile, @@ -35,7 +36,8 @@ import { BadRequestException, HttpException, Injectable, - Logger + Logger, + NotFoundException } from '@nestjs/common'; import { AssetClass, @@ -507,6 +509,18 @@ export class AdminService { }; } + public async getUser(id: string): Promise { + const [user] = await this.getUsersWithAnalytics({ + where: { id } + }); + + if (!user) { + throw new NotFoundException(`User with ID ${id} not found`); + } + + return user; + } + public async getUsers({ skip, take = Number.MAX_SAFE_INTEGER @@ -516,7 +530,15 @@ export class AdminService { }): Promise { const [count, users] = await Promise.all([ this.countUsersWithAnalytics(), - this.getUsersWithAnalytics({ skip, take }) + this.getUsersWithAnalytics({ + skip, + take, + where: { + NOT: { + analytics: null + } + } + }) ]); return { count, users }; @@ -814,17 +836,17 @@ export class AdminService { private async getUsersWithAnalytics({ skip, - take + take, + where }: { skip?: number; take?: number; + where?: Prisma.UserWhereInput; }): Promise { let orderBy: Prisma.Enumerable = [ { createdAt: 'desc' } ]; - let where: Prisma.UserWhereInput; - if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { orderBy = [ { @@ -833,12 +855,6 @@ export class AdminService { } } ]; - - where = { - NOT: { - analytics: null - } - }; } const usersWithAnalytics = await this.prismaService.user.findMany({ diff --git a/libs/common/src/lib/interfaces/admin-user.interface.ts b/libs/common/src/lib/interfaces/admin-user.interface.ts new file mode 100644 index 000000000..872abca90 --- /dev/null +++ b/libs/common/src/lib/interfaces/admin-user.interface.ts @@ -0,0 +1,13 @@ +import { Role } from '@prisma/client'; + +export interface AdminUser { + accountCount: number; + activityCount: number; + country: string; + createdAt: Date; + dailyApiRequests: number; + engagement: number; + id: string; + lastActivity: Date; + role: Role; +} diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 06ecf32e8..899813f30 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -7,6 +7,7 @@ import type { AdminMarketData, AdminMarketDataItem } from './admin-market-data.interface'; +import type { AdminUser } from './admin-user.interface'; import type { AssetClassSelectorOption } from './asset-class-selector-option.interface'; import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { BenchmarkProperty } from './benchmark-property.interface'; @@ -38,6 +39,7 @@ import type { AccountBalancesResponse } from './responses/account-balances-respo import type { AccountsResponse } from './responses/accounts-response.interface'; import type { ActivitiesResponse } from './responses/activities-response.interface'; import type { ActivityResponse } from './responses/activity-response.interface'; +import type { AdminUserResponse } from './responses/admin-user-response.interface'; import type { AdminUsersResponse } from './responses/admin-users-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface'; @@ -92,6 +94,8 @@ export { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + AdminUser, + AdminUserResponse, AdminUsersResponse, AiPromptResponse, ApiKeyResponse, diff --git a/libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts b/libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts new file mode 100644 index 000000000..8e93fc097 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts @@ -0,0 +1,3 @@ +import { AdminUser } from '../admin-user.interface'; + +export interface AdminUserResponse extends AdminUser {} diff --git a/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts b/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts index d9f58ee18..8dd058030 100644 --- a/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts @@ -1,16 +1,6 @@ -import { Role } from '@prisma/client'; +import { AdminUser } from '../admin-user.interface'; export interface AdminUsersResponse { count: number; - users: { - accountCount: number; - activityCount: number; - country: string; - createdAt: Date; - dailyApiRequests: number; - engagement: number; - id: string; - lastActivity: Date; - role: Role; - }[]; + users: AdminUser[]; } 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 081/146] 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 082/146] 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 083/146] 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 084/146] 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 085/146] 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 086/146] 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 087/146] 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 088/146] 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 089/146] 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 090/146] 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 091/146] 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 092/146] 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 093/146] 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 094/146] 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 095/146] 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 096/146] 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 097/146] 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 098/146] 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 099/146] 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 100/146] 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 101/146] 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 102/146] 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 103/146] 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 104/146] 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 105/146] 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 106/146] 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 107/146] 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 108/146] 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 109/146] 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 110/146] 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 129/146] 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 130/146] 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 131/146] 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 132/146] 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 133/146] 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 @@ }