diff --git a/.github/workflows/build-code.yml b/.github/workflows/build-code.yml index 7a8f72ac4..bd5d1efdc 100644 --- a/.github/workflows/build-code.yml +++ b/.github/workflows/build-code.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: node_version: - - 22 + - 22.22.1 steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index f52d28802..391160246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,94 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added the quantity column to the holdings table of the portfolio holdings page + +### Changed + +- Improved the allocations by ETF holding on the allocations page by refining the grouping of the same assets with diverging names (experimental) +- Improved the language localization for Polish (`pl`) +- Upgraded `@trivago/prettier-plugin-sort-imports` from version `5.2.2` to `6.0.2` + +### Fixed + +- Fixed an issue by adding a missing guard in the public access for portfolio sharing + +## 2.250.0 - 2026-03-17 + +### Added + +- Added support for specific calendar year date ranges (`2025`, `2024`, `2023`, etc.) on the portfolio activities page + +### Changed + +- Consolidated the sign-out logic within the user service to unify cookie, state and token clearance +- Improved the language localization for Polish (`pl`) +- Upgraded `@ionic/angular` from version `8.7.3` to `8.8.1` +- Upgraded `replace-in-file` from version `8.3.0` to `8.4.0` +- Upgraded `svgmap` from version `2.14.0` to `2.19.2` +- Pinned the _Node.js_ version in the _Build code_ _GitHub Action_ to ensure environment consistency for tests + +### Fixed + +- Fixed an issue with the detection of the thousand separator for the `de-CH` locale +- Fixed an issue in the _Storybook_ stories of the symbol autocomplete component caused by a circular dependency + +## 2.249.0 - 2026-03-10 + +### Added + +- Integrated _Bull Dashboard_ for a detailed jobs queue view in the admin control panel (experimental) +- Added a debounce to the `PortfolioChangedListener` and `AssetProfileChangedListener` to minimize redundant _Redis_ and database operations + +### Changed + +- Improved the _Storybook_ stories of the value component +- Improved the language localization for Dutch (`nl`) +- Improved the language localization for German (`de`) +- Upgraded `class-validator` from version `0.14.3` to `0.15.1` + +### Fixed + +- Fixed false _Redis_ health check failures by using unique keys and increasing the timeout to 5s + +## 2.248.0 - 2026-03-07 + +### Added + +- Added support for column sorting to the data providers management of the admin control panel + +### Changed + +- Included asset profile data in the endpoint `GET api/v1/portfolio/holdings` +- Included asset profile data in the holdings of the public page +- Reused the value component in the platform management of the admin control panel +- Reused the value component in the tag management of the admin control panel +- Deprecated the `api/v1/order` endpoints in favor of the `api/v1/activities` endpoints +- Upgraded `jsonpath` from version `1.1.1` to `1.2.1` + +### Fixed + +- Fixed an issue in the _FIRE_ calculator to correctly calculate the projected total amount + +## 2.247.0 - 2026-03-04 + +### Changed + +- Upgraded `yahoo-finance2` from version `3.13.0` to `3.13.2` + +## 2.246.0 - 2026-03-03 + ### Changed +- Removed the deprecated `committedFunds` from the summary of the portfolio details endpoint - Upgraded `Nx` from version `22.4.5` to `22.5.3` ### Fixed - Fixed the _Save_ button state in the rule settings dialog on the _X-ray_ page +- Fixed an issue where the apply and reset filter buttons remained disabled in the assistant ## 2.245.0 - 2026-03-01 diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/activities/activities.controller.ts similarity index 84% rename from apps/api/src/app/order/order.controller.ts rename to apps/api/src/app/activities/activities.controller.ts index c7021809e..141fd4c82 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/activities/activities.controller.ts @@ -37,28 +37,32 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; -import { Order as OrderModel, Prisma } from '@prisma/client'; +import { Order, Prisma } from '@prisma/client'; import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { OrderService } from './order.service'; +import { ActivitiesService } from './activities.service'; -@Controller('order') -export class OrderController { +@Controller([ + 'activities', + /** @deprecated */ + 'order' +]) +export class ActivitiesController { public constructor( + private readonly activitiesService: ActivitiesService, private readonly apiService: ApiService, private readonly dataProviderService: DataProviderService, private readonly dataGatheringService: DataGatheringService, private readonly impersonationService: ImpersonationService, - private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser ) {} @Delete() - @HasPermission(permissions.deleteOrder) + @HasPermission(permissions.deleteActivity) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) - public async deleteOrders( + public async deleteActivities( @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('dataSource') filterByDataSource?: string, @@ -73,29 +77,29 @@ export class OrderController { filterByTags }); - return this.orderService.deleteOrders({ + return this.activitiesService.deleteActivities({ filters, userId: this.request.user.id }); } @Delete(':id') - @HasPermission(permissions.deleteOrder) + @HasPermission(permissions.deleteActivity) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async deleteOrder(@Param('id') id: string): Promise { - const order = await this.orderService.order({ + public async deleteActivity(@Param('id') id: string): Promise { + const activity = await this.activitiesService.order({ id, userId: this.request.user.id }); - if (!order) { + if (!activity) { throw new HttpException( getReasonPhrase(StatusCodes.FORBIDDEN), StatusCodes.FORBIDDEN ); } - return this.orderService.deleteOrder({ + return this.activitiesService.deleteActivity({ id }); } @@ -105,7 +109,7 @@ export class OrderController { @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) - public async getAllOrders( + public async getAllActivities( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @@ -122,7 +126,7 @@ export class OrderController { let startDate: Date; if (dateRange) { - ({ endDate, startDate } = getIntervalFromDateRange(dateRange)); + ({ endDate, startDate } = getIntervalFromDateRange({ dateRange })); } const filters = this.apiService.buildFiltersFromQueryParams({ @@ -137,7 +141,7 @@ export class OrderController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.settings.settings.baseCurrency; - const { activities, count } = await this.orderService.getOrders({ + const { activities, count } = await this.activitiesService.getActivities({ endDate, filters, sortColumn, @@ -158,7 +162,7 @@ export class OrderController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) - public async getOrderById( + public async getActivityById( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('id') id: string ): Promise { @@ -166,7 +170,7 @@ export class OrderController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.settings.settings.baseCurrency; - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ userCurrency, includeDrafts: true, userId: impersonationUserId || this.request.user.id, @@ -187,11 +191,11 @@ export class OrderController { return activity; } - @HasPermission(permissions.createOrder) + @HasPermission(permissions.createActivity) @Post() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) - public async createOrder(@Body() data: CreateOrderDto): Promise { + public async createActivity(@Body() data: CreateOrderDto): Promise { try { await this.dataProviderService.validateActivities({ activitiesDto: [ @@ -227,7 +231,7 @@ export class OrderController { delete data.dataSource; - const order = await this.orderService.createOrder({ + const activity = await this.activitiesService.createActivity({ ...data, date: parseISO(data.date), SymbolProfile: { @@ -252,14 +256,14 @@ export class OrderController { userId: this.request.user.id }); - if (dataSource && !order.isDraft) { + if (dataSource && !activity.isDraft) { // Gather symbol data in the background, if data source is set // (not MANUAL) and not draft this.dataGatheringService.gatherSymbols({ dataGatheringItems: [ { dataSource, - date: order.date, + date: activity.date, symbol: data.symbol } ], @@ -267,19 +271,22 @@ export class OrderController { }); } - return order; + return activity; } - @HasPermission(permissions.updateOrder) + @HasPermission(permissions.updateActivity) @Put(':id') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) - public async update(@Param('id') id: string, @Body() data: UpdateOrderDto) { - const originalOrder = await this.orderService.order({ + public async updateActivity( + @Param('id') id: string, + @Body() data: UpdateOrderDto + ) { + const originalActivity = await this.activitiesService.order({ id }); - if (!originalOrder || originalOrder.userId !== this.request.user.id) { + if (!originalActivity || originalActivity.userId !== this.request.user.id) { throw new HttpException( getReasonPhrase(StatusCodes.FORBIDDEN), StatusCodes.FORBIDDEN @@ -302,7 +309,7 @@ export class OrderController { delete data.dataSource; - return this.orderService.updateOrder({ + return this.activitiesService.updateActivity({ data: { ...data, date, diff --git a/apps/api/src/app/order/order.module.ts b/apps/api/src/app/activities/activities.module.ts similarity index 86% rename from apps/api/src/app/order/order.module.ts rename to apps/api/src/app/activities/activities.module.ts index 9bc837aa6..7476ad66a 100644 --- a/apps/api/src/app/order/order.module.ts +++ b/apps/api/src/app/activities/activities.module.ts @@ -15,12 +15,12 @@ import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/sym import { Module } from '@nestjs/common'; -import { OrderController } from './order.controller'; -import { OrderService } from './order.service'; +import { ActivitiesController } from './activities.controller'; +import { ActivitiesService } from './activities.service'; @Module({ - controllers: [OrderController], - exports: [OrderService], + controllers: [ActivitiesController], + exports: [ActivitiesService], imports: [ ApiModule, CacheModule, @@ -35,6 +35,6 @@ import { OrderService } from './order.service'; TransformDataSourceInRequestModule, TransformDataSourceInResponseModule ], - providers: [AccountBalanceService, AccountService, OrderService] + providers: [AccountBalanceService, AccountService, ActivitiesService] }) -export class OrderModule {} +export class ActivitiesModule {} diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/activities/activities.service.ts similarity index 93% rename from apps/api/src/app/order/order.service.ts rename to apps/api/src/app/activities/activities.service.ts index 9a4f1e46b..89b9468f8 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -44,7 +44,7 @@ import { groupBy, uniqBy } from 'lodash'; import { randomUUID } from 'node:crypto'; @Injectable() -export class OrderService { +export class ActivitiesService { public constructor( private readonly accountBalanceService: AccountBalanceService, private readonly accountService: AccountService, @@ -62,7 +62,7 @@ export class OrderService { tags, userId }: { tags: Tag[]; userId: string } & AssetProfileIdentifier) { - const orders = await this.prismaService.order.findMany({ + const activities = await this.prismaService.order.findMany({ where: { userId, SymbolProfile: { @@ -73,7 +73,7 @@ export class OrderService { }); await Promise.all( - orders.map(({ id }) => + activities.map(({ id }) => this.prismaService.order.update({ data: { tags: { @@ -96,7 +96,7 @@ export class OrderService { ); } - public async createOrder( + public async createActivity( data: Prisma.OrderCreateInput & { accountId?: string; assetClass?: AssetClass; @@ -201,7 +201,7 @@ export class OrderService { ? false : isAfter(data.date as Date, endOfToday()); - const order = await this.prismaService.order.create({ + const activity = await this.prismaService.order.create({ data: { ...orderData, account, @@ -235,56 +235,56 @@ export class OrderService { this.eventEmitter.emit( AssetProfileChangedEvent.getName(), new AssetProfileChangedEvent({ - currency: order.SymbolProfile.currency, - dataSource: order.SymbolProfile.dataSource, - symbol: order.SymbolProfile.symbol + currency: activity.SymbolProfile.currency, + dataSource: activity.SymbolProfile.dataSource, + symbol: activity.SymbolProfile.symbol }) ); this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ - userId: order.userId + userId: activity.userId }) ); - return order; + return activity; } - public async deleteOrder( + public async deleteActivity( where: Prisma.OrderWhereUniqueInput ): Promise { - const order = await this.prismaService.order.delete({ + const activity = await this.prismaService.order.delete({ where }); const [symbolProfile] = await this.symbolProfileService.getSymbolProfilesByIds([ - order.symbolProfileId + activity.symbolProfileId ]); if (symbolProfile.activitiesCount === 0) { - await this.symbolProfileService.deleteById(order.symbolProfileId); + await this.symbolProfileService.deleteById(activity.symbolProfileId); } this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ - userId: order.userId + userId: activity.userId }) ); - return order; + return activity; } - public async deleteOrders({ + public async deleteActivities({ filters, userId }: { filters?: Filter[]; userId: string; }): Promise { - const { activities } = await this.getOrders({ + const { activities } = await this.getActivities({ filters, userId, includeDrafts: true, @@ -324,7 +324,7 @@ export class OrderService { } /** - * Generates synthetic orders for cash holdings based on account balance history. + * Generates synthetic activities for cash holdings based on account balance history. * Treat currencies as assets with a fixed unit price of 1.0 (in their own currency) to allow * performance tracking based on exchange rate fluctuations. * @@ -334,7 +334,7 @@ export class OrderService { * @param userId - The ID of the user. * @returns A response containing the list of synthetic cash activities. */ - public async getCashOrders({ + public async getCashActivities({ cashDetails, filters = [], userCurrency, @@ -448,7 +448,10 @@ export class OrderService { }; } - public async getLatestOrder({ dataSource, symbol }: AssetProfileIdentifier) { + public async getLatestActivity({ + dataSource, + symbol + }: AssetProfileIdentifier) { return this.prismaService.order.findFirst({ orderBy: { date: 'desc' @@ -459,7 +462,7 @@ export class OrderService { }); } - public async getOrders({ + public async getActivities({ endDate, filters, includeDrafts = false, @@ -742,17 +745,17 @@ export class OrderService { } /** - * Retrieves all orders required for the portfolio calculator, including both standard asset orders - * and optional synthetic orders representing cash activities. + * Retrieves all activities required for the portfolio calculator, including both standard asset activities + * and optional synthetic activities representing cash activities. */ @LogPerformance - public async getOrdersForPortfolioCalculator({ + public async getActivitiesForPortfolioCalculator({ filters, userCurrency, userId, withCash = false }: { - /** Optional filters to apply to the orders. */ + /** Optional filters to apply to the activities. */ filters?: Filter[]; /** The base currency of the user. */ userCurrency: string; @@ -761,7 +764,7 @@ export class OrderService { /** Whether to include cash activities in the result. */ withCash?: boolean; }) { - const orders = await this.getOrders({ + const activities = await this.getActivities({ filters, userCurrency, userId, @@ -775,18 +778,18 @@ export class OrderService { currency: userCurrency }); - const cashOrders = await this.getCashOrders({ + const cashActivities = await this.getCashActivities({ cashDetails, filters, userCurrency, userId }); - orders.activities.push(...cashOrders.activities); - orders.count += cashOrders.count; + activities.activities.push(...cashActivities.activities); + activities.count += cashActivities.count; } - return orders; + return activities; } public async getStatisticsByCurrency( @@ -817,7 +820,7 @@ export class OrderService { }); } - public async updateOrder({ + public async updateActivity({ data, where }: { @@ -882,7 +885,7 @@ export class OrderService { data: { tags: { set: [] } } }); - const order = await this.prismaService.order.update({ + const activity = await this.prismaService.order.update({ where, data: { ...data, @@ -896,11 +899,11 @@ export class OrderService { this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ - userId: order.userId + userId: activity.userId }) ); - return order; + return activity; } private async orders(params: { diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 8a202a926..69b619625 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -172,7 +172,7 @@ export class AdminController { let date: Date; if (dateRange) { - const { startDate } = getIntervalFromDateRange(dateRange); + const { startDate } = getIntervalFromDateRange({ dateRange }); date = startDate; } diff --git a/apps/api/src/app/admin/admin.module.ts b/apps/api/src/app/admin/admin.module.ts index 598b68f17..960a36629 100644 --- a/apps/api/src/app/admin/admin.module.ts +++ b/apps/api/src/app/admin/admin.module.ts @@ -1,4 +1,4 @@ -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module'; @@ -20,6 +20,7 @@ import { QueueModule } from './queue/queue.module'; @Module({ imports: [ + ActivitiesModule, ApiModule, BenchmarkModule, ConfigurationModule, @@ -28,7 +29,6 @@ import { QueueModule } from './queue/queue.module'; DemoModule, ExchangeRateDataModule, MarketDataModule, - OrderModule, PrismaModule, PropertyModule, QueueModule, diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 1f8745cd1..d2bf6e411 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; @@ -55,12 +55,12 @@ import { groupBy } from 'lodash'; @Injectable() export class AdminService { public constructor( + private readonly activitiesService: ActivitiesService, private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly symbolProfileService: SymbolProfileService @@ -475,7 +475,7 @@ export class AdminService { if (isCurrencyAssetProfile) { currency = getCurrencyFromSymbol(symbol); ({ activitiesCount, dateOfFirstActivity } = - await this.orderService.getStatisticsByCurrency(currency)); + await this.activitiesService.getStatisticsByCurrency(currency)); } const [[assetProfile], marketData] = await Promise.all([ @@ -798,7 +798,7 @@ export class AdminService { if (isCurrency(getCurrencyFromSymbol(symbol))) { currency = getCurrencyFromSymbol(symbol); ({ activitiesCount, dateOfFirstActivity } = - await this.orderService.getStatisticsByCurrency(currency)); + await this.activitiesService.getStatisticsByCurrency(currency)); } const lastMarketPrice = lastMarketPriceMap.get( diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 89f52e1ea..8ebe05928 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -1,4 +1,5 @@ import { EventsModule } from '@ghostfolio/api/events/events.module'; +import { BullBoardAuthMiddleware } from '@ghostfolio/api/middlewares/bull-board-auth.middleware'; import { HtmlTemplateMiddleware } from '@ghostfolio/api/middlewares/html-template.middleware'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { CronModule } from '@ghostfolio/api/services/cron/cron.module'; @@ -10,10 +11,13 @@ import { PropertyModule } from '@ghostfolio/api/services/property/property.modul import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module'; import { + BULL_BOARD_ROUTE, DEFAULT_LANGUAGE_CODE, SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config'; +import { ExpressAdapter } from '@bull-board/express'; +import { BullBoardModule } from '@bull-board/nestjs'; import { BullModule } from '@nestjs/bull'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -25,6 +29,7 @@ import { join } from 'node:path'; import { AccessModule } from './access/access.module'; import { AccountModule } from './account/account.module'; +import { ActivitiesModule } from './activities/activities.module'; import { AdminModule } from './admin/admin.module'; import { AppController } from './app.controller'; import { AssetModule } from './asset/asset.module'; @@ -48,7 +53,6 @@ import { HealthModule } from './health/health.module'; import { ImportModule } from './import/import.module'; import { InfoModule } from './info/info.module'; import { LogoModule } from './logo/logo.module'; -import { OrderModule } from './order/order.module'; import { PlatformModule } from './platform/platform.module'; import { PortfolioModule } from './portfolio/portfolio.module'; import { RedisCacheModule } from './redis-cache/redis-cache.module'; @@ -62,6 +66,7 @@ import { UserModule } from './user/user.module'; AdminModule, AccessModule, AccountModule, + ActivitiesModule, AiModule, ApiKeysModule, AssetModule, @@ -69,6 +74,29 @@ import { UserModule } from './user/user.module'; AuthDeviceModule, AuthModule, BenchmarksModule, + ...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true' + ? [ + BullBoardModule.forRoot({ + adapter: ExpressAdapter, + boardOptions: { + uiConfig: { + boardLogo: { + height: 0, + path: '', + width: 0 + }, + boardTitle: 'Job Queues', + favIcon: { + alternative: '/assets/favicon-32x32.png', + default: '/assets/favicon-32x32.png' + } + } + }, + middleware: BullBoardAuthMiddleware, + route: BULL_BOARD_ROUTE + }) + ] + : []), BullModule.forRoot({ redis: { db: parseInt(process.env.REDIS_DB ?? '0', 10), @@ -94,7 +122,6 @@ import { UserModule } from './user/user.module'; InfoModule, LogoModule, MarketDataModule, - OrderModule, PlatformModule, PlatformsModule, PortfolioModule, @@ -105,7 +132,12 @@ import { UserModule } from './user/user.module'; RedisCacheModule, ScheduleModule.forRoot(), ServeStaticModule.forRoot({ - exclude: ['/.well-known/*wildcard', '/api/*wildcard', '/sitemap.xml'], + exclude: [ + `${BULL_BOARD_ROUTE}/*wildcard`, + '/.well-known/*wildcard', + '/api/*wildcard', + '/sitemap.xml' + ], rootPath: join(__dirname, '..', 'client'), serveStaticOptions: { setHeaders: (res) => { diff --git a/apps/api/src/app/endpoints/ai/ai.module.ts b/apps/api/src/app/endpoints/ai/ai.module.ts index 8a441fde7..eab4ecf8b 100644 --- a/apps/api/src/app/endpoints/ai/ai.module.ts +++ b/apps/api/src/app/endpoints/ai/ai.module.ts @@ -1,6 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -29,6 +29,7 @@ import { AiService } from './ai.service'; @Module({ controllers: [AiController], imports: [ + ActivitiesModule, ApiModule, BenchmarkModule, ConfigurationModule, @@ -37,7 +38,6 @@ import { AiService } from './ai.service'; I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PortfolioSnapshotQueueModule, PrismaModule, PropertyModule, diff --git a/apps/api/src/app/endpoints/assets/assets.controller.ts b/apps/api/src/app/endpoints/assets/assets.controller.ts index a314b3f19..397686d8c 100644 --- a/apps/api/src/app/endpoints/assets/assets.controller.ts +++ b/apps/api/src/app/endpoints/assets/assets.controller.ts @@ -4,6 +4,7 @@ import { interpolate } from '@ghostfolio/common/helper'; import { Controller, Get, + OnModuleInit, Param, Res, Version, @@ -14,12 +15,14 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; @Controller('assets') -export class AssetsController { +export class AssetsController implements OnModuleInit { private webManifest = ''; public constructor( public readonly configurationService: ConfigurationService - ) { + ) {} + + public onModuleInit() { try { this.webManifest = readFileSync( join(__dirname, 'assets', 'site.webmanifest'), diff --git a/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts b/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts index 629d90928..970925777 100644 --- a/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts +++ b/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts @@ -126,10 +126,10 @@ export class BenchmarksController { @Query('tags') filterByTags?: string, @Query('withExcludedAccounts') withExcludedAccountsParam = 'false' ): Promise { - const { endDate, startDate } = getIntervalFromDateRange( + const { endDate, startDate } = getIntervalFromDateRange({ dateRange, - new Date(startDateString) - ); + startDate: new Date(startDateString) + }); const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, diff --git a/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts b/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts index 8bdf79035..2bcd6177d 100644 --- a/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts +++ b/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts @@ -1,6 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -32,6 +32,7 @@ import { BenchmarksService } from './benchmarks.service'; @Module({ controllers: [BenchmarksController], imports: [ + ActivitiesModule, ApiModule, ConfigurationModule, DataProviderModule, @@ -39,7 +40,6 @@ import { BenchmarksService } from './benchmarks.service'; I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PortfolioSnapshotQueueModule, PrismaModule, PropertyModule, diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index b4ecd37ba..b97640cab 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -1,5 +1,5 @@ import { AccessService } from '@ghostfolio/api/app/access/access.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; @@ -28,9 +28,9 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes'; export class PublicController { public constructor( private readonly accessService: AccessService, + private readonly activitiesService: ActivitiesService, private readonly configurationService: ConfigurationService, private readonly exchangeRateDataService: ExchangeRateDataService, - private readonly orderService: OrderService, private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly userService: UserService @@ -81,7 +81,7 @@ export class PublicController { }) ]); - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ sortColumn: 'date', sortDirection: 'desc', take: 10, @@ -167,6 +167,7 @@ export class PublicController { allocationInPercentage: portfolioPosition.valueInBaseCurrency / totalValue, assetClass: hasDetails ? portfolioPosition.assetClass : undefined, + assetProfile: hasDetails ? portfolioPosition.assetProfile : undefined, countries: hasDetails ? portfolioPosition.countries : [], currency: hasDetails ? portfolioPosition.currency : undefined, dataSource: portfolioPosition.dataSource, diff --git a/apps/api/src/app/endpoints/public/public.module.ts b/apps/api/src/app/endpoints/public/public.module.ts index 19e281dde..e8395228f 100644 --- a/apps/api/src/app/endpoints/public/public.module.ts +++ b/apps/api/src/app/endpoints/public/public.module.ts @@ -1,7 +1,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module'; import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -27,13 +27,13 @@ import { PublicController } from './public.controller'; controllers: [PublicController], imports: [ AccessModule, + ActivitiesModule, BenchmarkModule, DataProviderModule, ExchangeRateDataModule, I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PortfolioSnapshotQueueModule, PrismaModule, RedisCacheModule, diff --git a/apps/api/src/app/export/export.module.ts b/apps/api/src/app/export/export.module.ts index 4f40cc417..6158fe043 100644 --- a/apps/api/src/app/export/export.module.ts +++ b/apps/api/src/app/export/export.module.ts @@ -1,5 +1,5 @@ import { AccountModule } from '@ghostfolio/api/app/account/account.module'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; @@ -14,9 +14,9 @@ import { ExportService } from './export.service'; controllers: [ExportController], imports: [ AccountModule, + ActivitiesModule, ApiModule, MarketDataModule, - OrderModule, TagModule, TransformDataSourceInRequestModule ], diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index 55f8d7dc9..4f2fb3309 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -1,5 +1,5 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.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'; @@ -17,8 +17,8 @@ import { groupBy, uniqBy } from 'lodash'; export class ExportService { public constructor( private readonly accountService: AccountService, + private readonly activitiesService: ActivitiesService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, private readonly tagService: TagService ) {} @@ -38,7 +38,7 @@ export class ExportService { }); const platformsMap: { [platformId: string]: Platform } = {}; - let { activities } = await this.orderService.getOrders({ + let { activities } = await this.activitiesService.getActivities({ filters, userId, includeDrafts: true, diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 81481fd65..d5724bef2 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -38,7 +38,7 @@ export class ImportController { @Post() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - @HasPermission(permissions.createOrder) + @HasPermission(permissions.createActivity) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async import( diff --git a/apps/api/src/app/import/import.module.ts b/apps/api/src/app/import/import.module.ts index a4a13f941..ca9b5667b 100644 --- a/apps/api/src/app/import/import.module.ts +++ b/apps/api/src/app/import/import.module.ts @@ -1,6 +1,6 @@ import { AccountModule } from '@ghostfolio/api/app/account/account.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { CacheModule } from '@ghostfolio/api/app/cache/cache.module'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module'; import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; @@ -25,6 +25,7 @@ import { ImportService } from './import.service'; controllers: [ImportController], imports: [ AccountModule, + ActivitiesModule, ApiModule, CacheModule, ConfigurationModule, @@ -32,7 +33,6 @@ import { ImportService } from './import.service'; DataProviderModule, ExchangeRateDataModule, MarketDataModule, - OrderModule, PlatformModule, PortfolioModule, PrismaModule, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 497b8a7e9..b82f763a0 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -1,5 +1,5 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { ApiService } from '@ghostfolio/api/services/api/api.service'; @@ -44,12 +44,12 @@ import { ImportDataDto } from './import-data.dto'; export class ImportService { public constructor( private readonly accountService: AccountService, + private readonly activitiesService: ActivitiesService, private readonly apiService: ApiService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, private readonly platformService: PlatformService, private readonly portfolioService: PortfolioService, private readonly symbolProfileService: SymbolProfileService, @@ -91,7 +91,7 @@ export class ImportService { userId, withExcludedAccounts: true }), - this.orderService.getOrders({ + this.activitiesService.getActivities({ filters, userCurrency, userId, @@ -548,7 +548,7 @@ export class ImportService { continue; } - order = await this.orderService.createOrder({ + order = await this.activitiesService.createActivity({ comment, currency, date, @@ -645,7 +645,7 @@ export class ImportService { userId: string; }): Promise[]> { const { activities: existingActivities } = - await this.orderService.getOrders({ + await this.activitiesService.getActivities({ userCurrency, userId, includeDrafts: true, diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 553cb8c90..d57b85d8c 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -158,10 +158,10 @@ export abstract class PortfolioCalculator { this.redisCacheService = redisCacheService; this.userId = userId; - const { endDate, startDate } = getIntervalFromDateRange( - 'max', - subDays(dateOfFirstActivity, 1) - ); + const { endDate, startDate } = getIntervalFromDateRange({ + dateRange: 'max', + startDate: subDays(dateOfFirstActivity, 1) + }); this.endDate = endOfDay(endDate); this.startDate = startOfDay(startDate); @@ -885,7 +885,7 @@ export abstract class PortfolioCalculator { // Make sure some key dates are present for (const dateRange of ['1d', '1y', '5y', 'max', 'mtd', 'wtd', 'ytd']) { const { endDate: dateRangeEnd, startDate: dateRangeStart } = - getIntervalFromDateRange(dateRange); + getIntervalFromDateRange({ dateRange }); if ( !isBefore(dateRangeStart, startDate) && diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index a53ebcf05..217a67c49 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -1,6 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; @@ -62,11 +62,11 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { describe('PortfolioCalculator', () => { let accountBalanceService: AccountBalanceService; let accountService: AccountService; + let activitiesService: ActivitiesService; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; let dataProviderService: DataProviderService; let exchangeRateDataService: ExchangeRateDataService; - let orderService: OrderService; let portfolioCalculatorFactory: PortfolioCalculatorFactory; let portfolioSnapshotService: PortfolioSnapshotService; let redisCacheService: RedisCacheService; @@ -106,13 +106,13 @@ describe('PortfolioCalculator', () => { ); currentRateService = new CurrentRateService( - dataProviderService, null, + dataProviderService, null, null ); - orderService = new OrderService( + activitiesService = new ActivitiesService( accountBalanceService, accountService, null, @@ -183,18 +183,17 @@ describe('PortfolioCalculator', () => { .spyOn(dataProviderService, 'getDataSourceForExchangeRates') .mockReturnValue(DataSource.YAHOO); - jest.spyOn(orderService, 'getOrders').mockResolvedValue({ + jest.spyOn(activitiesService, 'getActivities').mockResolvedValue({ activities: [], count: 0 }); - const { activities } = await orderService.getOrdersForPortfolioCalculator( - { + const { activities } = + await activitiesService.getActivitiesForPortfolioCalculator({ userCurrency: 'CHF', userId: userDummyData.id, withCash: true - } - ); + }); jest.spyOn(currentRateService, 'getValues').mockResolvedValue({ dataProviderInfos: [], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts index be69048df..2841e9975 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -860,7 +860,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { return format(date, 'yyyy'); }) ] as DateRange[]) { - const dateInterval = getIntervalFromDateRange(dateRange); + const dateInterval = getIntervalFromDateRange({ dateRange }); const endDate = dateInterval.endDate; let startDate = dateInterval.startDate; diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index d8b7482e7..5f2358679 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -114,9 +114,9 @@ describe('CurrentRateService', () => { marketDataService = new MarketDataService(null); currentRateService = new CurrentRateService( + null, dataProviderService, marketDataService, - null, null ); }); diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index 5d39a54bb..b454b01cd 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; @@ -24,9 +24,9 @@ export class CurrentRateService { private static readonly MARKET_DATA_PAGE_SIZE = 50000; public constructor( + private readonly activitiesService: ActivitiesService, private readonly dataProviderService: DataProviderService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser ) {} @@ -129,10 +129,11 @@ export class CurrentRateService { if (!value) { // Fallback to unit price of latest activity - const latestActivity = await this.orderService.getLatestOrder({ - dataSource, - symbol - }); + const latestActivity = + await this.activitiesService.getLatestActivity({ + dataSource, + symbol + }); value = { dataSource, diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 5ed3c0009..9c41aecb9 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { @@ -63,10 +63,10 @@ import { UpdateHoldingTagsDto } from './update-holding-tags.dto'; @Controller('portfolio') export class PortfolioController { public constructor( + private readonly activitiesService: ActivitiesService, private readonly apiService: ApiService, private readonly configurationService: ConfigurationService, private readonly impersonationService: ImpersonationService, - private readonly orderService: OrderService, private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser ) {} @@ -187,7 +187,6 @@ export class PortfolioController { portfolioSummary = nullifyValuesInObject(summary, [ 'cash', - 'committedFunds', 'currentNetWorth', 'currentValueInBaseCurrency', 'dividendInBaseCurrency', @@ -321,9 +320,9 @@ export class PortfolioController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.settings.settings.baseCurrency; - const { endDate, startDate } = getIntervalFromDateRange(dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ endDate, filters, startDate, @@ -640,7 +639,7 @@ export class PortfolioController { return report; } - @HasPermission(permissions.updateOrder) + @HasPermission(permissions.updateActivity) @Put('holding/:dataSource/:symbol/tags') @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts index 6dd5811a3..65a9b71aa 100644 --- a/apps/api/src/app/portfolio/portfolio.module.ts +++ b/apps/api/src/app/portfolio/portfolio.module.ts @@ -1,7 +1,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module'; import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { PerformanceLoggingModule } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.module'; @@ -34,6 +34,7 @@ import { RulesService } from './rules.service'; exports: [PortfolioService], imports: [ AccessModule, + ActivitiesModule, ApiModule, BenchmarkModule, ConfigurationModule, @@ -43,7 +44,6 @@ import { RulesService } from './rules.service'; I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PerformanceLoggingModule, PortfolioSnapshotQueueModule, PrismaModule, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 17bd01ce4..60b413cf9 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1,7 +1,7 @@ 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 { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; @@ -105,13 +105,13 @@ export class PortfolioService { public constructor( private readonly accountBalanceService: AccountBalanceService, private readonly accountService: AccountService, + private readonly activitiesService: ActivitiesService, private readonly benchmarkService: BenchmarkService, private readonly calculatorFactory: PortfolioCalculatorFactory, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly i18nService: I18nService, private readonly impersonationService: ImpersonationService, - private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly rulesService: RulesService, private readonly symbolProfileService: SymbolProfileService, @@ -403,10 +403,10 @@ export class PortfolioService { const user = await this.userService.user({ id: userId }); const userCurrency = this.getUserCurrency(user); - const { endDate, startDate } = getIntervalFromDateRange(dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ filters, userCurrency, userId @@ -490,7 +490,7 @@ export class PortfolioService { ); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ filters, userCurrency, userId @@ -623,6 +623,28 @@ export class PortfolioService { ? 0 : valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(), assetClass: assetProfile.assetClass, + assetProfile: { + assetClass: assetProfile.assetClass, + assetSubClass: assetProfile.assetSubClass, + countries: assetProfile.countries, + currency: assetProfile.currency, + dataSource: assetProfile.dataSource, + holdings: assetProfile.holdings.map( + ({ allocationInPercentage, name }) => { + return { + allocationInPercentage, + name, + valueInBaseCurrency: valueInBaseCurrency + .mul(allocationInPercentage) + .toNumber() + }; + } + ), + name: assetProfile.name, + sectors: assetProfile.sectors, + symbol: assetProfile.symbol, + url: assetProfile.url + }, assetSubClass: assetProfile.assetSubClass, countries: assetProfile.countries, dataSource: assetProfile.dataSource, @@ -758,7 +780,7 @@ export class PortfolioService { const userCurrency = this.getUserCurrency(user); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ userCurrency, userId }); @@ -988,7 +1010,7 @@ export class PortfolioService { userId, userCurrency }), - this.orderService.getOrdersForPortfolioCalculator({ + this.activitiesService.getActivitiesForPortfolioCalculator({ filters, userCurrency, userId @@ -1025,7 +1047,7 @@ export class PortfolioService { const { errors, hasErrors, historicalData } = await portfolioCalculator.getSnapshot(); - const { endDate, startDate } = getIntervalFromDateRange(dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); const { chart } = await portfolioCalculator.getPerformance({ end: endDate, @@ -1349,7 +1371,12 @@ export class PortfolioService { }) { userId = await this.getUserId(impersonationId, userId); - await this.orderService.assignTags({ dataSource, symbol, tags, userId }); + await this.activitiesService.assignTags({ + dataSource, + symbol, + tags, + userId + }); } private getAggregatedMarkets(holdings: Record): { @@ -1672,6 +1699,17 @@ export class PortfolioService { allocationInPercentage: 0, assetClass: AssetClass.LIQUIDITY, assetSubClass: AssetSubClass.CASH, + assetProfile: { + currency, + assetClass: AssetClass.LIQUIDITY, + assetSubClass: AssetSubClass.CASH, + countries: [], + dataSource: undefined, + holdings: [], + name: currency, + sectors: [], + symbol: currency + }, countries: [], dataSource: undefined, dateOfFirstActivity: undefined, @@ -1841,7 +1879,7 @@ export class PortfolioService { userId = await this.getUserId(impersonationId, userId); const user = await this.userService.user({ id: userId }); - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ userCurrency, userId, withExcludedAccountsAndActivities: true @@ -1914,8 +1952,6 @@ export class PortfolioService { .plus(emergencyFundHoldingsValueInBaseCurrency) .toNumber(); - const committedFunds = new Big(totalBuy).minus(totalSell); - const totalOfExcludedActivities = this.getSumOfActivityType({ userCurrency, activities: excludedActivities, @@ -1979,7 +2015,6 @@ export class PortfolioService { activityCount: activities.filter(({ type }) => { return ['BUY', 'SELL'].includes(type); }).length, - committedFunds: committedFunds.toNumber(), currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(), dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), emergencyFund: { diff --git a/apps/api/src/app/redis-cache/redis-cache.service.ts b/apps/api/src/app/redis-cache/redis-cache.service.ts index 1ea0a6137..619d23fc5 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.ts @@ -6,7 +6,7 @@ import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; import { Inject, Injectable, Logger } from '@nestjs/common'; import Keyv from 'keyv'; import ms from 'ms'; -import { createHash } from 'node:crypto'; +import { createHash, randomUUID } from 'node:crypto'; @Injectable() export class RedisCacheService { @@ -75,13 +75,16 @@ export class RedisCacheService { } public async isHealthy() { - const testKey = '__health_check__'; + const HEALTH_CHECK_TIMEOUT = ms('5 seconds'); + + const testKey = `__health_check__${randomUUID().replace(/-/g, '')}`; const testValue = Date.now().toString(); try { await Promise.race([ (async () => { - await this.set(testKey, testValue, ms('1 second')); + await this.set(testKey, testValue, HEALTH_CHECK_TIMEOUT); + const result = await this.get(testKey); if (result !== testValue) { @@ -91,7 +94,7 @@ export class RedisCacheService { new Promise((_, reject) => setTimeout( () => reject(new Error('Redis health check failed: timeout')), - ms('2 seconds') + HEALTH_CHECK_TIMEOUT ) ) ]); diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 7ca68d275..3f4e898fc 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -1,4 +1,4 @@ -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; @@ -18,6 +18,7 @@ import { UserService } from './user.service'; controllers: [UserController], exports: [UserService], imports: [ + ActivitiesModule, ConfigurationModule, I18nModule, ImpersonationModule, @@ -25,7 +26,6 @@ import { UserService } from './user.service'; secret: process.env.JWT_SECRET_KEY, signOptions: { expiresIn: '30 days' } }), - OrderModule, PrismaModule, PropertyModule, RedactValuesInResponseModule, diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 97ab8a59f..370f5d422 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; @@ -55,10 +55,10 @@ import { createHmac } from 'node:crypto'; @Injectable() export class UserService { public constructor( + private readonly activitiesService: ActivitiesService, private readonly configurationService: ConfigurationService, private readonly eventEmitter: EventEmitter2, private readonly i18nService: I18nService, - private readonly orderService: OrderService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly subscriptionService: SubscriptionService, @@ -530,8 +530,14 @@ export class UserService { } } - if (!environment.production && hasRole(user, Role.ADMIN)) { - currentPermissions.push(permissions.impersonateAllUsers); + if (hasRole(user, Role.ADMIN)) { + if (this.configurationService.get('ENABLE_FEATURE_BULL_BOARD')) { + currentPermissions.push(permissions.accessAdminControlBullBoard); + } + + if (!environment.production) { + currentPermissions.push(permissions.impersonateAllUsers); + } } user.accounts = user.accounts.sort((a, b) => { @@ -643,7 +649,7 @@ export class UserService { } catch {} try { - await this.orderService.deleteOrders({ + await this.activitiesService.deleteActivities({ userId: where.id }); } catch {} diff --git a/apps/api/src/events/asset-profile-changed.event.ts b/apps/api/src/events/asset-profile-changed.event.ts index 46a8c5db4..c08fe59f1 100644 --- a/apps/api/src/events/asset-profile-changed.event.ts +++ b/apps/api/src/events/asset-profile-changed.event.ts @@ -8,4 +8,16 @@ export class AssetProfileChangedEvent { public static getName(): string { return 'assetProfile.changed'; } + + public getCurrency() { + return this.data.currency; + } + + public getDataSource() { + return this.data.dataSource; + } + + public getSymbol() { + return this.data.symbol; + } } diff --git a/apps/api/src/events/asset-profile-changed.listener.ts b/apps/api/src/events/asset-profile-changed.listener.ts index ad80ee4a5..cc70edad6 100644 --- a/apps/api/src/events/asset-profile-changed.listener.ts +++ b/apps/api/src/events/asset-profile-changed.listener.ts @@ -1,29 +1,74 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.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 { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; +import { DataSource } from '@prisma/client'; +import ms from 'ms'; import { AssetProfileChangedEvent } from './asset-profile-changed.event'; @Injectable() export class AssetProfileChangedListener { + private static readonly DEBOUNCE_DELAY = ms('5 seconds'); + + private debounceTimers = new Map(); + public constructor( + private readonly activitiesService: ActivitiesService, private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, - private readonly exchangeRateDataService: ExchangeRateDataService, - private readonly orderService: OrderService + private readonly exchangeRateDataService: ExchangeRateDataService ) {} @OnEvent(AssetProfileChangedEvent.getName()) - public async handleAssetProfileChanged(event: AssetProfileChangedEvent) { + public handleAssetProfileChanged(event: AssetProfileChangedEvent) { + const currency = event.getCurrency(); + const dataSource = event.getDataSource(); + const symbol = event.getSymbol(); + + const key = getAssetProfileIdentifier({ + dataSource, + symbol + }); + + const existingTimer = this.debounceTimers.get(key); + + if (existingTimer) { + clearTimeout(existingTimer); + } + + this.debounceTimers.set( + key, + setTimeout(() => { + this.debounceTimers.delete(key); + + void this.processAssetProfileChanged({ + currency, + dataSource, + symbol + }); + }, AssetProfileChangedListener.DEBOUNCE_DELAY) + ); + } + + private async processAssetProfileChanged({ + currency, + dataSource, + symbol + }: { + currency: string; + dataSource: DataSource; + symbol: string; + }) { Logger.log( - `Asset profile of ${event.data.symbol} (${event.data.dataSource}) has changed`, + `Asset profile of ${symbol} (${dataSource}) has changed`, 'AssetProfileChangedListener' ); @@ -31,16 +76,16 @@ export class AssetProfileChangedListener { this.configurationService.get( 'ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES' ) === false || - event.data.currency === DEFAULT_CURRENCY + currency === DEFAULT_CURRENCY ) { return; } const existingCurrencies = this.exchangeRateDataService.getCurrencies(); - if (!existingCurrencies.includes(event.data.currency)) { + if (!existingCurrencies.includes(currency)) { Logger.log( - `New currency ${event.data.currency} has been detected`, + `New currency ${currency} has been detected`, 'AssetProfileChangedListener' ); @@ -48,13 +93,13 @@ export class AssetProfileChangedListener { } const { dateOfFirstActivity } = - await this.orderService.getStatisticsByCurrency(event.data.currency); + await this.activitiesService.getStatisticsByCurrency(currency); if (dateOfFirstActivity) { await this.dataGatheringService.gatherSymbol({ dataSource: this.dataProviderService.getDataSourceForExchangeRates(), date: dateOfFirstActivity, - symbol: `${DEFAULT_CURRENCY}${event.data.currency}` + symbol: `${DEFAULT_CURRENCY}${currency}` }); } } diff --git a/apps/api/src/events/events.module.ts b/apps/api/src/events/events.module.ts index ece67ebe0..772766945 100644 --- a/apps/api/src/events/events.module.ts +++ b/apps/api/src/events/events.module.ts @@ -1,4 +1,4 @@ -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.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'; @@ -12,11 +12,11 @@ import { PortfolioChangedListener } from './portfolio-changed.listener'; @Module({ imports: [ + ActivitiesModule, ConfigurationModule, DataGatheringModule, DataProviderModule, ExchangeRateDataModule, - OrderModule, RedisCacheModule ], providers: [AssetProfileChangedListener, PortfolioChangedListener] diff --git a/apps/api/src/events/portfolio-changed.listener.ts b/apps/api/src/events/portfolio-changed.listener.ts index d12b9558d..f8e2a9229 100644 --- a/apps/api/src/events/portfolio-changed.listener.ts +++ b/apps/api/src/events/portfolio-changed.listener.ts @@ -2,22 +2,44 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; +import ms from 'ms'; import { PortfolioChangedEvent } from './portfolio-changed.event'; @Injectable() export class PortfolioChangedListener { + private static readonly DEBOUNCE_DELAY = ms('5 seconds'); + + private debounceTimers = new Map(); + public constructor(private readonly redisCacheService: RedisCacheService) {} @OnEvent(PortfolioChangedEvent.getName()) handlePortfolioChangedEvent(event: PortfolioChangedEvent) { + const userId = event.getUserId(); + + const existingTimer = this.debounceTimers.get(userId); + + if (existingTimer) { + clearTimeout(existingTimer); + } + + this.debounceTimers.set( + userId, + setTimeout(() => { + this.debounceTimers.delete(userId); + + void this.processPortfolioChanged({ userId }); + }, PortfolioChangedListener.DEBOUNCE_DELAY) + ); + } + + private async processPortfolioChanged({ userId }: { userId: string }) { Logger.log( - `Portfolio of user '${event.getUserId()}' has changed`, + `Portfolio of user '${userId}' has changed`, 'PortfolioChangedListener' ); - this.redisCacheService.removePortfolioSnapshotsByUserId({ - userId: event.getUserId() - }); + await this.redisCacheService.removePortfolioSnapshotsByUserId({ userId }); } } diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts index eed261f3e..ba8760c70 100644 --- a/apps/api/src/helper/object.helper.spec.ts +++ b/apps/api/src/helper/object.helper.spec.ts @@ -1540,7 +1540,6 @@ describe('redactAttributes', () => { netPerformanceWithCurrencyEffect: null, totalBuy: null, totalSell: null, - committedFunds: null, currentValueInBaseCurrency: null, dividendInBaseCurrency: null, emergencyFund: null, @@ -3017,7 +3016,6 @@ describe('redactAttributes', () => { netPerformanceWithCurrencyEffect: null, totalBuy: null, totalSell: null, - committedFunds: null, currentValueInBaseCurrency: null, dividendInBaseCurrency: null, emergencyFund: null, diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index 3cccd6efa..57643f76c 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -68,6 +68,7 @@ export class TransformDataSourceInResponseInterceptor< 'errors[*].dataSource', 'fearAndGreedIndex.CRYPTOCURRENCIES.dataSource', 'fearAndGreedIndex.STOCKS.dataSource', + 'holdings[*].assetProfile.dataSource', 'holdings[*].dataSource', 'items[*].dataSource', 'SymbolProfile.dataSource', diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index a8de3dc5e..f08a09a83 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,4 +1,5 @@ import { + BULL_BOARD_ROUTE, DEFAULT_HOST, DEFAULT_PORT, STORYBOOK_PATH, @@ -14,6 +15,7 @@ import { import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import type { NestExpressApplication } from '@nestjs/platform-express'; +import cookieParser from 'cookie-parser'; import { NextFunction, Request, Response } from 'express'; import helmet from 'helmet'; @@ -46,6 +48,7 @@ async function bootstrap() { }); app.setGlobalPrefix('api', { exclude: [ + `${BULL_BOARD_ROUTE.substring(1)}{/*wildcard}`, 'sitemap.xml', ...SUPPORTED_LANGUAGE_CODES.map((languageCode) => { // Exclude language-specific routes with an optional wildcard @@ -53,6 +56,7 @@ async function bootstrap() { }) ] }); + app.useGlobalPipes( new ValidationPipe({ forbidNonWhitelisted: true, @@ -64,6 +68,8 @@ async function bootstrap() { // Support 10mb csv/json files for importing activities app.useBodyParser('json', { limit: '10mb' }); + app.use(cookieParser()); + if (configService.get('ENABLE_FEATURE_SUBSCRIPTION') === 'true') { app.use((req: Request, res: Response, next: NextFunction) => { if (req.path.startsWith(STORYBOOK_PATH)) { diff --git a/apps/api/src/middlewares/bull-board-auth.middleware.ts b/apps/api/src/middlewares/bull-board-auth.middleware.ts new file mode 100644 index 000000000..432deb974 --- /dev/null +++ b/apps/api/src/middlewares/bull-board-auth.middleware.ts @@ -0,0 +1,28 @@ +import { BULL_BOARD_COOKIE_NAME } from '@ghostfolio/common/config'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; + +import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common'; +import { NextFunction, Request, Response } from 'express'; +import passport from 'passport'; + +@Injectable() +export class BullBoardAuthMiddleware implements NestMiddleware { + public use(req: Request, res: Response, next: NextFunction) { + const token = req.cookies?.[BULL_BOARD_COOKIE_NAME]; + + if (token) { + req.headers.authorization = `Bearer ${token}`; + } + + passport.authenticate('jwt', { session: false }, (error, user) => { + if ( + error || + !hasPermission(user?.permissions, permissions.accessAdminControl) + ) { + next(new ForbiddenException()); + } else { + next(); + } + })(req, res, next); + } +} diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index 5f9d1055d..ad8e84a99 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -30,6 +30,7 @@ export class ConfigurationService { API_KEY_FINANCIAL_MODELING_PREP: str({ default: '' }), API_KEY_OPEN_FIGI: str({ default: '' }), API_KEY_RAPID_API: str({ default: '' }), + BULL_BOARD_IS_READ_ONLY: bool({ default: true }), CACHE_QUOTES_TTL: num({ default: ms('1 minute') }), CACHE_TTL: num({ default: CACHE_TTL_NO_CACHE }), DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }), @@ -43,6 +44,7 @@ export class ConfigurationService { ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }), ENABLE_FEATURE_AUTH_OIDC: bool({ default: false }), ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }), + ENABLE_FEATURE_BULL_BOARD: bool({ default: false }), 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 }), 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 6030e62d4..40b45a115 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 @@ -16,7 +16,7 @@ import { LookupResponse } from '@ghostfolio/common/interfaces'; -import { Injectable } from '@nestjs/common'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import Alphavantage from 'alphavantage'; import { format, isAfter, isBefore, parse } from 'date-fns'; @@ -24,12 +24,16 @@ import { format, isAfter, isBefore, parse } from 'date-fns'; import { AlphaVantageHistoricalResponse } from './interfaces/interfaces'; @Injectable() -export class AlphaVantageService implements DataProviderInterface { +export class AlphaVantageService + implements DataProviderInterface, OnModuleInit +{ public alphaVantage; public constructor( private readonly configurationService: ConfigurationService - ) { + ) {} + + public onModuleInit() { this.alphaVantage = Alphavantage({ key: this.configurationService.get('API_KEY_ALPHA_VANTAGE') }); 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 d0d96acac..d5ed69d06 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -17,7 +17,7 @@ import { LookupResponse } from '@ghostfolio/common/interfaces'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { AssetClass, AssetSubClass, @@ -27,13 +27,15 @@ import { import { format, fromUnixTime, getUnixTime } from 'date-fns'; @Injectable() -export class CoinGeckoService implements DataProviderInterface { - private readonly apiUrl: string; - private readonly headers: HeadersInit = {}; +export class CoinGeckoService implements DataProviderInterface, OnModuleInit { + private apiUrl: string; + private headers: HeadersInit = {}; public constructor( private readonly configurationService: ConfigurationService - ) { + ) {} + + public onModuleInit() { const apiKeyDemo = this.configurationService.get('API_KEY_COINGECKO_DEMO'); const apiKeyPro = this.configurationService.get('API_KEY_COINGECKO_PRO'); 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 cd20fca44..8c718108c 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 @@ -22,7 +22,7 @@ import { } from '@ghostfolio/common/interfaces'; import { MarketState } from '@ghostfolio/common/types'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { AssetClass, AssetSubClass, @@ -33,14 +33,18 @@ import { addDays, format, isSameDay, isToday } from 'date-fns'; import { isNumber } from 'lodash'; @Injectable() -export class EodHistoricalDataService implements DataProviderInterface { +export class EodHistoricalDataService + implements DataProviderInterface, OnModuleInit +{ private apiKey: string; private readonly URL = 'https://eodhistoricaldata.com/api'; public constructor( private readonly configurationService: ConfigurationService, private readonly symbolProfileService: SymbolProfileService - ) { + ) {} + + public onModuleInit() { this.apiKey = this.configurationService.get('API_KEY_EOD_HISTORICAL_DATA'); } 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 2b4193af5..27391130e 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 @@ -23,7 +23,7 @@ import { } from '@ghostfolio/common/interfaces'; import { MarketState } from '@ghostfolio/common/types'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { AssetClass, AssetSubClass, @@ -44,7 +44,9 @@ import { import { uniqBy } from 'lodash'; @Injectable() -export class FinancialModelingPrepService implements DataProviderInterface { +export class FinancialModelingPrepService + implements DataProviderInterface, OnModuleInit +{ private static countriesMapping = { 'Korea (the Republic of)': 'South Korea', 'Russian Federation': 'Russia', @@ -57,7 +59,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { private readonly configurationService: ConfigurationService, private readonly cryptocurrencyService: CryptocurrencyService, private readonly prismaService: PrismaService - ) { + ) {} + + public onModuleInit() { this.apiKey = this.configurationService.get( 'API_KEY_FINANCIAL_MODELING_PREP' ); diff --git a/apps/api/src/services/i18n/i18n.service.ts b/apps/api/src/services/i18n/i18n.service.ts index cf340d7c6..1cdb811a9 100644 --- a/apps/api/src/services/i18n/i18n.service.ts +++ b/apps/api/src/services/i18n/i18n.service.ts @@ -1,16 +1,16 @@ import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import * as cheerio from 'cheerio'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; @Injectable() -export class I18nService { +export class I18nService implements OnModuleInit { private localesPath = join(__dirname, 'assets', 'locales'); private translations: { [locale: string]: cheerio.CheerioAPI } = {}; - public constructor() { + public onModuleInit() { this.loadFiles(); } diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 57c58898e..9664ae144 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -10,6 +10,7 @@ export interface Environment extends CleanedEnvAccessors { API_KEY_FINANCIAL_MODELING_PREP: string; API_KEY_OPEN_FIGI: string; API_KEY_RAPID_API: string; + BULL_BOARD_IS_READ_ONLY: boolean; CACHE_QUOTES_TTL: number; CACHE_TTL: number; DATA_SOURCE_EXCHANGE_RATES: string; @@ -19,6 +20,7 @@ export interface Environment extends CleanedEnvAccessors { ENABLE_FEATURE_AUTH_GOOGLE: boolean; ENABLE_FEATURE_AUTH_OIDC: boolean; ENABLE_FEATURE_AUTH_TOKEN: boolean; + ENABLE_FEATURE_BULL_BOARD: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean; diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts index b51823476..f251c8d0c 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts @@ -9,6 +9,8 @@ import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathe import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { DATA_GATHERING_QUEUE } from '@ghostfolio/common/config'; +import { BullAdapter } from '@bull-board/api/bullAdapter'; +import { BullBoardModule } from '@bull-board/nestjs'; import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; import ms from 'ms'; @@ -17,6 +19,18 @@ import { DataGatheringProcessor } from './data-gathering.processor'; @Module({ imports: [ + ...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true' + ? [ + BullBoardModule.forFeature({ + adapter: BullAdapter, + name: DATA_GATHERING_QUEUE, + options: { + displayName: 'Data Gathering', + readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + } + }) + ] + : []), BullModule.registerQueue({ limiter: { duration: ms('4 seconds'), diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts index 958636334..1260f1cf0 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts @@ -1,5 +1,5 @@ import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; @@ -13,6 +13,8 @@ import { PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE } from '@ghostfolio/common/config'; +import { BullAdapter } from '@bull-board/api/bullAdapter'; +import { BullBoardModule } from '@bull-board/nestjs'; import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; @@ -22,6 +24,19 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor'; exports: [BullModule, PortfolioSnapshotService], imports: [ AccountBalanceModule, + ActivitiesModule, + ...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true' + ? [ + BullBoardModule.forFeature({ + adapter: BullAdapter, + name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE, + options: { + displayName: 'Portfolio Snapshot Computation', + readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + } + }) + ] + : []), BullModule.registerQueue({ name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE, settings: { @@ -36,7 +51,6 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor'; DataProviderModule, ExchangeRateDataModule, MarketDataModule, - OrderModule, RedisCacheModule ], providers: [ 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 58a0a8f8a..f3aa6e77e 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 @@ -1,5 +1,5 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; @@ -23,9 +23,9 @@ import { PortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue export class PortfolioSnapshotProcessor { public constructor( private readonly accountBalanceService: AccountBalanceService, + private readonly activitiesService: ActivitiesService, private readonly calculatorFactory: PortfolioCalculatorFactory, private readonly configurationService: ConfigurationService, - private readonly orderService: OrderService, private readonly redisCacheService: RedisCacheService ) {} @@ -47,7 +47,7 @@ export class PortfolioSnapshotProcessor { ); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ filters: job.data.filters, userCurrency: job.data.userCurrency, userId: job.data.userId, diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts index ee951820d..b424f7198 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts @@ -10,19 +10,21 @@ import { resolveMarketCondition } from '@ghostfolio/common/helper'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { isWeekend } from 'date-fns'; import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2'; @Injectable() -export class TwitterBotService { +export class TwitterBotService implements OnModuleInit { private twitterClient: TwitterApiReadWrite; public constructor( private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, private readonly symbolService: SymbolService - ) { + ) {} + + public onModuleInit() { this.twitterClient = new TwitterApi({ accessSecret: this.configurationService.get( 'TWITTER_ACCESS_TOKEN_SECRET' diff --git a/apps/client/proxy.conf.json b/apps/client/proxy.conf.json index a31371d9f..825965b92 100644 --- a/apps/client/proxy.conf.json +++ b/apps/client/proxy.conf.json @@ -1,4 +1,8 @@ { + "/admin/queues": { + "target": "http://0.0.0.0:3333", + "secure": false + }, "/api": { "target": "http://0.0.0.0:3333", "secure": false diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index a4af01124..201c2f994 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -10,12 +10,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, DOCUMENT, HostBinding, - Inject, - OnDestroy, + inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatDialog } from '@angular/material/dialog'; import { Title } from '@angular/platform-browser'; import { @@ -30,15 +31,13 @@ import { DataSource } from '@prisma/client'; import { addIcons } from 'ionicons'; import { openOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { filter, takeUntil } from 'rxjs/operators'; +import { filter } 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 { GfAppQueryParams } from './interfaces/interfaces'; import { ImpersonationStorageService } from './services/impersonation-storage.service'; -import { TokenStorageService } from './services/token-storage.service'; import { UserService } from './services/user/user.service'; @Component({ @@ -48,11 +47,7 @@ import { UserService } from './services/user/user.service'; styleUrls: ['./app.component.scss'], templateUrl: './app.component.html' }) -export class GfAppComponent implements OnDestroy, OnInit { - @HostBinding('class.has-info-message') get getHasMessage() { - return this.hasInfoMessage; - } - +export class GfAppComponent implements OnInit { public canCreateAccount: boolean; public currentRoute: string; public currentSubRoute: string; @@ -67,52 +62,54 @@ export class GfAppComponent implements OnDestroy, OnInit { public pageTitle: string; public routerLinkRegister = publicRoutes.register.routerLink; public showFooter = false; - public user: User; - - private unsubscribeSubject = new Subject(); - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private deviceService: DeviceDetectorService, - private dialog: MatDialog, - @Inject(DOCUMENT) private document: Document, - private impersonationStorageService: ImpersonationStorageService, - private notificationService: NotificationService, - private route: ActivatedRoute, - private router: Router, - private title: Title, - private tokenStorageService: TokenStorageService, - private userService: UserService - ) { + public user: User | undefined; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceService = inject(DeviceDetectorService); + private readonly dialog = inject(MatDialog); + private readonly document = inject(DOCUMENT); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly notificationService = inject(NotificationService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly title = inject(Title); + private readonly userService = inject(UserService); + + public constructor() { this.initializeTheme(); this.user = undefined; this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((params) => { - if ( - params['dataSource'] && - params['holdingDetailDialog'] && - params['symbol'] - ) { - this.openHoldingDetailDialog({ - dataSource: params['dataSource'], - symbol: params['symbol'] - }); + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe( + ({ dataSource, holdingDetailDialog, symbol }: GfAppQueryParams) => { + if (dataSource && holdingDetailDialog && symbol) { + this.openHoldingDetailDialog({ + dataSource, + symbol + }); + } } - }); + ); addIcons({ openOutline }); } + @HostBinding('class.has-info-message') get getHasMessage() { + return this.hasInfoMessage; + } + public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.info = this.dataService.fetchInfo(); this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); @@ -131,7 +128,7 @@ export class GfAppComponent implements OnDestroy, OnInit { !this.currentSubRoute) || (this.currentRoute === internalRoutes.home.path && this.currentSubRoute === - internalRoutes.home.subRoutes.holdings.path) || + internalRoutes.home.subRoutes?.holdings.path) || (this.currentRoute === internalRoutes.portfolio.path && !this.currentSubRoute)) && this.user?.settings?.viewMode !== 'ZEN' @@ -144,18 +141,18 @@ export class GfAppComponent implements OnDestroy, OnInit { if ( (this.currentRoute === internalRoutes.home.path && this.currentSubRoute === - internalRoutes.home.subRoutes.holdings.path) || + internalRoutes.home.subRoutes?.holdings.path) || (this.currentRoute === internalRoutes.portfolio.path && !this.currentSubRoute) || (this.currentRoute === internalRoutes.portfolio.path && this.currentSubRoute === - internalRoutes.portfolio.subRoutes.activities.path) || + internalRoutes.portfolio.subRoutes?.activities.path) || (this.currentRoute === internalRoutes.portfolio.path && this.currentSubRoute === - internalRoutes.portfolio.subRoutes.allocations.path) || + internalRoutes.portfolio.subRoutes?.allocations.path) || (this.currentRoute === internalRoutes.zen.path && this.currentSubRoute === - internalRoutes.home.subRoutes.holdings.path) + internalRoutes.home.subRoutes?.holdings.path) ) { this.hasPermissionToChangeFilters = true; } else { @@ -201,7 +198,7 @@ export class GfAppComponent implements OnDestroy, OnInit { }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { this.user = state.user; @@ -226,31 +223,31 @@ export class GfAppComponent implements OnDestroy, OnInit { } public onClickSystemMessage() { - if (this.user.systemMessage.routerLink) { - this.router.navigate(this.user.systemMessage.routerLink); + const systemMessage = this.user?.systemMessage; + + if (!systemMessage) { + return; + } + + if (systemMessage.routerLink) { + void this.router.navigate(systemMessage.routerLink); } else { this.notificationService.alert({ - title: this.user.systemMessage.message + title: systemMessage.message }); } } public onCreateAccount() { - this.tokenStorageService.signOut(); + this.userService.signOut(); } public onSignOut() { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initializeTheme(userPreferredColorScheme?: ColorScheme) { const isDarkTheme = userPreferredColorScheme ? userPreferredColorScheme === 'DARK' @@ -274,14 +271,11 @@ export class GfAppComponent implements OnDestroy, OnInit { }) { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open< - GfHoldingDetailDialogComponent, - HoldingDetailDialogParams - >(GfHoldingDetailDialogComponent, { + const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, { autoFocus: false, data: { dataSource, @@ -296,15 +290,21 @@ export class GfAppComponent implements OnDestroy, OnInit { ), hasPermissionToCreateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.createOrder) && + hasPermission( + this.user?.permissions, + permissions.createActivity + ) && !this.user?.settings?.isRestrictedView, hasPermissionToReportDataGlitch: hasPermission( this.user?.permissions, permissions.reportDataGlitch ), - hasPermissionToUpdateOrder: + hasPermissionToUpdateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.updateOrder) && + hasPermission( + this.user?.permissions, + permissions.updateActivity + ) && !this.user?.settings?.isRestrictedView, locale: this.user?.settings?.locale }, @@ -314,9 +314,9 @@ export class GfAppComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { - this.router.navigate([], { + void this.router.navigate([], { queryParams: { dataSource: null, holdingDetailDialog: null, @@ -342,6 +342,6 @@ export class GfAppComponent implements OnDestroy, OnInit { this.document .querySelector('meta[name="theme-color"]') - .setAttribute('content', themeColor); + ?.setAttribute('content', themeColor); } } 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 380fb69cb..7a3040391 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 @@ -26,11 +26,12 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Inject, - OnDestroy, OnInit } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog'; @@ -49,8 +50,7 @@ import { } from 'ionicons/icons'; import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { forkJoin, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { forkJoin } from 'rxjs'; import { AccountDetailDialogParams } from './interfaces/interfaces'; @@ -77,7 +77,7 @@ import { AccountDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./account-detail-dialog.component.scss'], templateUrl: 'account-detail-dialog.html' }) -export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { +export class GfAccountDetailDialogComponent implements OnInit { public accountBalances: AccountBalancesResponse['balances']; public activities: OrderWithAccount[]; public activitiesCount: number; @@ -104,18 +104,17 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { public user: User; public valueInBaseCurrency: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: AccountDetailDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private router: Router, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -154,7 +153,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { public onAddAccountBalance(accountBalance: CreateAccountBalanceDto) { this.dataService .postAccountBalance(accountBalance) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); @@ -163,7 +162,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { public onDeleteAccountBalance(aId: string) { this.dataService .deleteAccountBalance(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); @@ -176,7 +175,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { this.dataService .fetchExport({ activityIds }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { downloadAsFile({ content: data, @@ -212,7 +211,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { private fetchAccount() { this.dataService .fetchAccount(this.data.accountId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ activitiesCount, @@ -287,7 +286,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { sortColumn: this.sortColumn, sortDirection: this.sortDirection }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities, count }) => { this.dataSource = new MatTableDataSource(activities); this.totalItems = count; @@ -304,7 +303,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { forkJoin({ accountBalances: this.dataService .fetchAccountBalances(this.data.accountId) - .pipe(takeUntil(this.unsubscribeSubject)), + .pipe(takeUntilDestroyed(this.destroyRef)), portfolioPerformance: this.dataService .fetchPortfolioPerformance({ filters: [ @@ -317,7 +316,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { withExcludedAccounts: true, withItems: true }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) }).subscribe({ error: () => { this.isLoadingChart = false; @@ -360,7 +359,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { } ] }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.holdings = holdings; @@ -374,9 +373,4 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { this.fetchChart(); this.fetchPortfolioHoldings(); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts index de70a7b6e..b9f4d590d 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.component.ts @@ -1,5 +1,8 @@ +import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { + BULL_BOARD_COOKIE_NAME, + BULL_BOARD_ROUTE, DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_LOW, DATA_GATHERING_QUEUE_PRIORITY_MEDIUM, @@ -7,6 +10,7 @@ import { } from '@ghostfolio/common/config'; import { getDateWithTimeFormatString } from '@ghostfolio/common/helper'; import { AdminJobs, User } from '@ghostfolio/common/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { AdminService } from '@ghostfolio/ui/services'; @@ -15,10 +19,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, @@ -41,6 +46,7 @@ import { chevronUpCircleOutline, ellipsisHorizontal, ellipsisVertical, + openOutline, pauseOutline, playOutline, removeCircleOutline, @@ -48,8 +54,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -69,7 +73,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./admin-jobs.scss'], templateUrl: './admin-jobs.html' }) -export class GfAdminJobsComponent implements OnDestroy, OnInit { +export class GfAdminJobsComponent implements OnInit { @ViewChild(MatSort) sort: MatSort; public DATA_GATHERING_QUEUE_PRIORITY_LOW = DATA_GATHERING_QUEUE_PRIORITY_LOW; @@ -81,6 +85,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { public dataSource = new MatTableDataSource(); public defaultDateTimeFormat: string; public filterForm: FormGroup; + public displayedColumns = [ 'index', 'type', @@ -93,21 +98,24 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { 'status', 'actions' ]; + + public hasPermissionToAccessBullBoard = false; public isLoading = false; public statusFilterOptions = QUEUE_JOB_STATUS_LIST; - public user: User; - private unsubscribeSubject = new Subject(); + private user: User; public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private formBuilder: FormBuilder, private notificationService: NotificationService, + private tokenStorageService: TokenStorageService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -115,6 +123,11 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { this.defaultDateTimeFormat = getDateWithTimeFormatString( this.user.settings.locale ); + + this.hasPermissionToAccessBullBoard = hasPermission( + this.user.permissions, + permissions.accessAdminControlBullBoard + ); } }); @@ -126,6 +139,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { chevronUpCircleOutline, ellipsisHorizontal, ellipsisVertical, + openOutline, pauseOutline, playOutline, removeCircleOutline, @@ -139,7 +153,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { }); this.filterForm.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { const currentFilter = this.filterForm.get('status').value; this.fetchJobs(currentFilter ? [currentFilter] : undefined); @@ -151,7 +165,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { public onDeleteJob(aId: string) { this.adminService .deleteJob(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(); }); @@ -162,7 +176,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { this.adminService .deleteJobs({ status: currentFilter ? [currentFilter] : undefined }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(currentFilter ? [currentFilter] : undefined); }); @@ -171,12 +185,24 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { public onExecuteJob(aId: string) { this.adminService .executeJob(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(); }); } + public onOpenBullBoard() { + const token = this.tokenStorageService.getToken(); + + document.cookie = [ + `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`, + 'path=/', + 'SameSite=Strict' + ].join('; '); + + window.open(BULL_BOARD_ROUTE, '_blank'); + } + public onViewData(aData: AdminJobs['jobs'][0]['data']) { this.notificationService.alert({ title: JSON.stringify(aData, null, ' ') @@ -189,17 +215,12 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchJobs(aStatus?: JobStatus[]) { this.isLoading = true; this.adminService .fetchJobs({ status: aStatus }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ jobs }) => { this.dataSource = new MatTableDataSource(jobs); this.dataSource.sort = this.sort; 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 a82294001..cff80498c 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.html +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.html @@ -1,6 +1,15 @@
+ @if (hasPermissionToAccessBullBoard) { +
+ +
+ } +
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 0beca6f3c..d3265946f 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 @@ -26,10 +26,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialog } from '@angular/material/dialog'; @@ -63,7 +64,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; -import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged } from 'rxjs/operators'; import { AdminMarketDataService } from './admin-market-data.service'; import { GfAssetProfileDialogComponent } from './asset-profile-dialog/asset-profile-dialog.component'; @@ -95,9 +96,7 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in styleUrls: ['./admin-market-data.scss'], templateUrl: './admin-market-data.html' }) -export class GfAdminMarketDataComponent - implements AfterViewInit, OnDestroy, OnInit -{ +export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -166,13 +165,12 @@ export class GfAdminMarketDataComponent public totalItems = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( public adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private route: ActivatedRoute, @@ -209,7 +207,7 @@ export class GfAdminMarketDataComponent this.displayedColumns.push('actions'); this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if ( params['assetProfileDialog'] && @@ -226,7 +224,7 @@ export class GfAdminMarketDataComponent }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -238,7 +236,7 @@ export class GfAdminMarketDataComponent }); this.filters$ - .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) .subscribe((filters) => { this.activeFilters = filters; @@ -302,7 +300,7 @@ export class GfAdminMarketDataComponent public onGather7Days() { this.adminService .gather7Days() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -313,7 +311,7 @@ export class GfAdminMarketDataComponent public onGatherMax() { this.adminService .gatherMax() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -324,7 +322,7 @@ export class GfAdminMarketDataComponent public onGatherProfileData() { this.adminService .gatherProfileData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -334,14 +332,14 @@ export class GfAdminMarketDataComponent }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -358,11 +356,6 @@ export class GfAdminMarketDataComponent }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private loadData( { pageIndex, @@ -399,7 +392,7 @@ export class GfAdminMarketDataComponent skip: pageIndex * this.pageSize, take: this.pageSize }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ count, marketData }) => { this.totalItems = count; @@ -430,7 +423,7 @@ export class GfAdminMarketDataComponent }) { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -452,7 +445,7 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( (newAssetProfileIdentifier: AssetProfileIdentifier | undefined) => { if (newAssetProfileIdentifier) { @@ -468,7 +461,7 @@ export class GfAdminMarketDataComponent private openCreateAssetProfileDialog() { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -486,7 +479,7 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((result) => { if (!result) { this.router.navigate(['.'], { relativeTo: this.route }); @@ -499,7 +492,7 @@ export class GfAdminMarketDataComponent if (addAssetProfile && dataSource && symbol) { this.adminService .addAssetProfile({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.loadData(); }); 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 b1519e35b..c0af46e22 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 @@ -37,12 +37,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, ElementRef, Inject, - OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AbstractControl, FormBuilder, @@ -87,8 +88,8 @@ import { serverOutline } from 'ionicons/icons'; import ms from 'ms'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { AssetProfileDialogParams } from './interfaces/interfaces'; @@ -121,7 +122,7 @@ import { AssetProfileDialogParams } from './interfaces/interfaces'; styleUrls: ['./asset-profile-dialog.component.scss'], templateUrl: 'asset-profile-dialog.html' }) -export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { +export class GfAssetProfileDialogComponent implements OnInit { private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( new Date(), DATE_FORMAT @@ -241,14 +242,13 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public user: User; - private unsubscribeSubject = new Subject(); - public constructor( public adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private formBuilder: FormBuilder, private notificationService: NotificationService, @@ -282,7 +282,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ settings }) => { this.isDataGatheringEnabled = settings[PROPERTY_IS_DATA_GATHERING_ENABLED] === false ? false : true; @@ -291,7 +291,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -300,7 +300,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { this.assetProfileForm .get('assetClass') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((assetClass) => { const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; @@ -323,7 +323,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ assetProfile, marketData }) => { this.assetProfile = assetProfile; @@ -436,7 +436,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -449,7 +449,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { } & AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, range, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -462,7 +462,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .postBenchmark({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dataService.updateInfo(); @@ -664,7 +664,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { const newAssetProfileIdentifier = { @@ -714,7 +714,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ price }) => { this.notificationService.alert({ @@ -745,7 +745,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .deleteBenchmark({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dataService.updateInfo(); @@ -755,11 +755,6 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - public onTriggerSubmitAssetProfileForm() { if (this.assetProfileForm.valid) { this.onSubmitAssetProfileForm(); 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 fbf8afa03..35887abb4 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 @@ -10,9 +10,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AbstractControl, FormBuilder, @@ -31,7 +32,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatRadioModule } from '@angular/material/radio'; import { DataSource } from '@prisma/client'; import { isISO4217CurrencyCode } from 'class-validator'; -import { Subject, switchMap, takeUntil } from 'rxjs'; +import { switchMap } from 'rxjs'; import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; @@ -52,19 +53,19 @@ import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; styleUrls: ['./create-asset-profile-dialog.component.scss'], templateUrl: 'create-asset-profile-dialog.html' }) -export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { +export class GfCreateAssetProfileDialogComponent implements OnInit { public createAssetProfileForm: FormGroup; public ghostfolioPrefix = `${ghostfolioPrefix}_`; public mode: CreateAssetProfileDialogMode; private customCurrencies: string[]; private dataSourceForExchangeRates: DataSource; - private unsubscribeSubject = new Subject(); public constructor( public readonly adminService: AdminService, private readonly changeDetectorRef: ChangeDetectorRef, private readonly dataService: DataService, + private readonly destroyRef: DestroyRef, public readonly dialogRef: MatDialogRef, public readonly formBuilder: FormBuilder ) {} @@ -125,7 +126,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { symbol: `${DEFAULT_CURRENCY}${currency}` }); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.dialogRef.close({ @@ -154,11 +155,6 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { return false; } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private atLeastOneValid(control: AbstractControl): ValidationErrors { const addCurrencyControl = control.get('addCurrency'); const addSymbolControl = control.get('addSymbol'); @@ -189,7 +185,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { private initialize() { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataProviders, settings }) => { this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; 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 c0ccb0f64..5d4e5268e 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 @@ -22,7 +22,13 @@ import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; @@ -50,8 +56,6 @@ import { trashOutline } from 'ionicons/icons'; import ms, { StringValue } from 'ms'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -72,7 +76,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./admin-overview.scss'], templateUrl: './admin-overview.html' }) -export class GfAdminOverviewComponent implements OnDestroy, OnInit { +export class GfAdminOverviewComponent implements OnInit { public activitiesCount: number; public couponDuration: StringValue = '14 days'; public coupons: Coupon[]; @@ -88,13 +92,12 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { public user: User; public version: string; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private cacheService: CacheService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private snackBar: MatSnackBar, private userService: UserService @@ -102,7 +105,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { this.info = this.dataService.fetchInfo(); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -219,7 +222,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { confirmFn: () => { this.cacheService .flush() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -268,7 +271,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { public onSyncDemoUserAccount() { this.adminService .syncDemoUserAccount() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.snackBar.open( '✅ ' + $localize`Demo user account has been synced.`, @@ -280,15 +283,10 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchAdminData() { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activitiesCount, settings, userCount, version }) => { this.activitiesCount = activitiesCount; this.coupons = (settings[PROPERTY_COUPONS] as Coupon[]) ?? []; @@ -320,7 +318,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { .putAdminSetting(key, { value: value || value === false ? JSON.stringify(value) : undefined }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); 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 367827878..7370a19ae 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 @@ -52,7 +52,11 @@ Accounts - {{ element.accountCount }} + 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 02a2eed64..b8f2af789 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,18 +1,22 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; +import { getLocale } from '@ghostfolio/common/helper'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { AdminService, DataService } from '@ghostfolio/ui/services'; +import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, + Input, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -29,7 +33,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; 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'; @@ -38,6 +41,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfEntityLogoComponent, + GfValueComponent, IonIcon, MatButtonModule, MatMenuModule, @@ -49,7 +53,9 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- styleUrls: ['./admin-platform.component.scss'], templateUrl: './admin-platform.component.html' }) -export class GfAdminPlatformComponent implements OnDestroy, OnInit { +export class GfAdminPlatformComponent implements OnInit { + @Input() locale = getLocale(); + @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); @@ -57,12 +63,11 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { public displayedColumns = ['name', 'url', 'accounts', 'actions']; public platforms: Platform[]; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private notificationService: NotificationService, @@ -71,7 +76,7 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createPlatformDialog']) { this.openCreatePlatformDialog(); @@ -113,20 +118,15 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deletePlatform(aId: string) { this.adminService .deletePlatform(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); @@ -137,7 +137,7 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { private fetchPlatforms() { this.adminService .fetchPlatforms() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platforms) => { this.platforms = platforms; @@ -169,17 +169,17 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platform: CreatePlatformDto | null) => { if (platform) { this.adminService .postPlatform(platform) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); @@ -217,17 +217,17 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platform: UpdatePlatformDto | null) => { if (platform) { this.adminService .putPlatform(platform) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); 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 dfcf300c1..0cfe026a4 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 @@ -2,12 +2,7 @@ import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -23,7 +18,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; import { CreateOrUpdatePlatformDialogParams } from './interfaces/interfaces'; @@ -43,11 +37,9 @@ import { CreateOrUpdatePlatformDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-platform-dialog.scss'], templateUrl: 'create-or-update-platform-dialog.html' }) -export class GfCreateOrUpdatePlatformDialogComponent implements OnDestroy { +export class GfCreateOrUpdatePlatformDialogComponent { public platformForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdatePlatformDialogParams, public dialogRef: MatDialogRef, @@ -90,9 +82,4 @@ export class GfCreateOrUpdatePlatformDialogComponent implements OnDestroy { console.error(error); } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.html b/apps/client/src/app/components/admin-settings/admin-settings.component.html index af64f034b..76af96c4e 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.html +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.html @@ -40,9 +40,21 @@ } - +
- 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 ca7950291..506736156 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,17 +1,21 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; +import { getLocale } from '@ghostfolio/common/helper'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; +import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, + Input, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -28,7 +32,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; 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'; @@ -36,6 +39,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ + GfValueComponent, IonIcon, MatButtonModule, MatMenuModule, @@ -47,7 +51,9 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int styleUrls: ['./admin-tag.component.scss'], templateUrl: './admin-tag.component.html' }) -export class GfAdminTagComponent implements OnDestroy, OnInit { +export class GfAdminTagComponent implements OnInit { + @Input() locale = getLocale(); + @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); @@ -55,11 +61,10 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { public displayedColumns = ['name', 'userId', 'activities', 'actions']; public tags: Tag[]; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private notificationService: NotificationService, @@ -68,7 +73,7 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createTagDialog']) { this.openCreateTagDialog(); @@ -110,20 +115,15 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deleteTag(aId: string) { this.dataService .deleteTag(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); @@ -134,7 +134,7 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { private fetchTags() { this.dataService .fetchTags() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tags) => { this.tags = tags; @@ -165,17 +165,17 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag: CreateTagDto | null) => { if (tag) { this.dataService .postTag(tag) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); @@ -204,17 +204,17 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag: UpdateTagDto | null) => { if (tag) { this.dataService .putTag(tag) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); 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 323609a48..e22c73478 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,12 +1,7 @@ import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -21,7 +16,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; import { CreateOrUpdateTagDialogParams } from './interfaces/interfaces'; @@ -40,11 +34,9 @@ import { CreateOrUpdateTagDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-tag-dialog.scss'], templateUrl: 'create-or-update-tag-dialog.html' }) -export class GfCreateOrUpdateTagDialogComponent implements OnDestroy { +export class GfCreateOrUpdateTagDialogComponent { public tagForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateTagDialogParams, public dialogRef: MatDialogRef, @@ -85,9 +77,4 @@ export class GfCreateOrUpdateTagDialogComponent implements OnDestroy { console.error(error); } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 d479f2037..3b57ba1c8 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,7 +1,6 @@ import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; import { 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 { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -26,10 +25,11 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -56,8 +56,7 @@ import { } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { switchMap, takeUntil, tap } from 'rxjs/operators'; +import { switchMap, tap } from 'rxjs/operators'; @Component({ imports: [ @@ -76,7 +75,7 @@ import { switchMap, takeUntil, tap } from 'rxjs/operators'; styleUrls: ['./admin-users.scss'], templateUrl: './admin-users.html' }) -export class GfAdminUsersComponent implements OnDestroy, OnInit { +export class GfAdminUsersComponent implements OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; public dataSource = new MatTableDataSource(); @@ -90,23 +89,21 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public isLoading = false; public pageSize = DEFAULT_PAGE_SIZE; public routerLinkAdminControlUsers = - internalRoutes.adminControl.subRoutes.users.routerLink; + internalRoutes.adminControl.subRoutes?.users.routerLink; public totalItems = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, 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; @@ -141,7 +138,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { this.userService.stateChanged .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), tap((state) => { if (state?.user) { this.user = state.user; @@ -206,7 +203,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { confirmFn: () => { this.dataService .deleteUser(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['..'], { relativeTo: this.route }); }); @@ -224,13 +221,12 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { confirmFn: () => { this.dataService .updateUserAccessToken(aUserId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ accessToken }) => { this.notificationService.alert({ discardFn: () => { if (aUserId === this.user.id) { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; } @@ -261,11 +257,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchUsers({ pageIndex }: { pageIndex: number } = { pageIndex: 0 }) { this.isLoading = true; @@ -278,7 +269,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { skip: pageIndex * this.pageSize, take: this.pageSize }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ count, users }) => { this.dataSource = new MatTableDataSource(users); this.totalItems = count; @@ -308,7 +299,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { if (data?.action === 'delete' && data?.userId) { this.onDeleteUser(data.userId); diff --git a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts index e44e81be9..a77d65961 100644 --- a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts +++ b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts @@ -4,13 +4,14 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, + DestroyRef, Input, - OnDestroy, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import type { DataSource } from '@prisma/client'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { catchError, map, type Observable, of, Subject, takeUntil } from 'rxjs'; +import { catchError, map, type Observable, of } from 'rxjs'; import { DataProviderStatus } from './interfaces/interfaces'; @@ -20,14 +21,15 @@ import { DataProviderStatus } from './interfaces/interfaces'; selector: 'gf-data-provider-status', templateUrl: './data-provider-status.component.html' }) -export class GfDataProviderStatusComponent implements OnDestroy, OnInit { +export class GfDataProviderStatusComponent implements OnInit { @Input() dataSource: DataSource; public status$: Observable; - private unsubscribeSubject = new Subject(); - - public constructor(private dataService: DataService) {} + public constructor( + private dataService: DataService, + private destroyRef: DestroyRef + ) {} public ngOnInit() { this.status$ = this.dataService @@ -39,12 +41,7 @@ export class GfDataProviderStatusComponent implements OnDestroy, OnInit { catchError(() => { return of({ isHealthy: false }); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/footer/footer.component.ts b/apps/client/src/app/components/footer/footer.component.ts index 48f010565..d06e711b9 100644 --- a/apps/client/src/app/components/footer/footer.component.ts +++ b/apps/client/src/app/components/footer/footer.component.ts @@ -33,13 +33,13 @@ export class GfFooterComponent implements OnChanges { public hasPermissionToAccessFearAndGreedIndex: boolean; public routerLinkAbout = publicRoutes.about.routerLink; public routerLinkAboutChangelog = - publicRoutes.about.subRoutes.changelog.routerLink; + publicRoutes.about.subRoutes?.changelog.routerLink; public routerLinkAboutLicense = - publicRoutes.about.subRoutes.license.routerLink; + publicRoutes.about.subRoutes?.license.routerLink; public routerLinkAboutPrivacyPolicy = - publicRoutes.about.subRoutes.privacyPolicy.routerLink; + publicRoutes.about.subRoutes?.privacyPolicy.routerLink; public routerLinkAboutTermsOfService = - publicRoutes.about.subRoutes.termsOfService.routerLink; + publicRoutes.about.subRoutes?.termsOfService.routerLink; public routerLinkBlog = publicRoutes.blog.routerLink; public routerLinkFaq = publicRoutes.faq.routerLink; public routerLinkFeatures = publicRoutes.features.routerLink; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 9b003a590..ab329251f 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -24,6 +24,7 @@ import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, EventEmitter, HostListener, Input, @@ -31,6 +32,7 @@ import { Output, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; @@ -48,8 +50,8 @@ import { radioButtonOffOutline, radioButtonOnOutline } from 'ionicons/icons'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -131,10 +133,9 @@ export class GfHeaderComponent implements OnChanges { public routerLinkRegister = publicRoutes.register.routerLink; public routerLinkResources = publicRoutes.resources.routerLink; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, + private destroyRef: DestroyRef, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, private layoutService: LayoutService, @@ -146,7 +147,7 @@ export class GfHeaderComponent implements OnChanges { ) { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; this.impersonationId = impersonationId; @@ -224,11 +225,11 @@ export class GfHeaderComponent implements OnChanges { public onDateRangeChange(dateRange: DateRange) { this.dataService .putUserSetting({ dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); }); } @@ -252,11 +253,11 @@ export class GfHeaderComponent implements OnChanges { this.dataService .putUserSetting(userSetting) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); }); } @@ -301,7 +302,7 @@ export class GfHeaderComponent implements OnChanges { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { if (data?.accessToken) { this.dataService @@ -314,7 +315,7 @@ export class GfHeaderComponent implements OnChanges { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ authToken }) => { this.setToken(authToken); @@ -331,7 +332,7 @@ export class GfHeaderComponent implements OnChanges { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { const userLanguage = user?.settings?.language; @@ -342,9 +343,4 @@ export class GfHeaderComponent implements OnChanges { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 427386796..13ded73eb 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 @@ -35,10 +35,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Inject, - OnDestroy, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; @@ -67,8 +68,7 @@ import { walletOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { switchMap, takeUntil } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import { HoldingDetailDialogParams } from './interfaces/interfaces'; @@ -102,7 +102,7 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./holding-detail-dialog.component.scss'], templateUrl: 'holding-detail-dialog.html' }) -export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { +export class GfHoldingDetailDialogComponent implements OnInit { public activitiesCount: number; public accounts: Account[]; public assetClass: string; @@ -158,11 +158,10 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { public user: User; public value: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: HoldingDetailDialogParams, private formBuilder: FormBuilder, @@ -192,7 +191,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.holdingForm .get('tags') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tags: Tag[]) => { const newTag = tags.find(({ id }) => { return id === undefined; @@ -217,7 +216,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { switchMap(() => { return this.userService.get(true); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(); } else { @@ -227,7 +226,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } }); @@ -236,7 +235,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { .fetchAccounts({ filters }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ accounts }) => { this.accounts = accounts; @@ -249,7 +248,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { sortColumn: this.sortColumn, sortDirection: this.sortDirection }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities }) => { this.dataSource = new MatTableDataSource(activities); @@ -261,7 +260,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ activitiesCount, @@ -524,7 +523,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -581,8 +580,8 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { }; this.dataService - .postOrder(activity) - .pipe(takeUntil(this.unsubscribeSubject)) + .postActivity(activity) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate( internalRoutes.portfolio.subRoutes.activities.routerLink @@ -599,7 +598,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.dataService .fetchExport({ activityIds }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { downloadAsFile({ content: data, @@ -629,18 +628,13 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.dialogRef.close(); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchMarketData() { this.dataService .fetchMarketDataBySymbol({ dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketData }) => { this.marketDataItems = marketData; 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 86f4676f3..15415e413 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 @@ -414,7 +414,7 @@ diff --git a/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts index aab854384..527b13636 100644 --- a/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts @@ -11,7 +11,7 @@ export interface HoldingDetailDialogParams { hasPermissionToAccessAdminControl: boolean; hasPermissionToCreateActivity: boolean; hasPermissionToReportDataGlitch: boolean; - hasPermissionToUpdateOrder: boolean; + hasPermissionToUpdateActivity: boolean; locale: string; symbol: string; } diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index dc444977d..19a48ccd8 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -19,9 +19,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; @@ -30,8 +31,6 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { gridOutline, reorderFourOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -51,7 +50,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-holdings.scss'], templateUrl: './home-holdings.html' }) -export class GfHomeHoldingsComponent implements OnDestroy, OnInit { +export class GfHomeHoldingsComponent implements OnInit { public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE'; public deviceType: string; @@ -71,11 +70,10 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE ); - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private router: Router, @@ -89,13 +87,13 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -107,7 +105,7 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { this.hasPermissionToCreateActivity = hasPermission( this.user.permissions, - permissions.createOrder + permissions.createActivity ); this.initialize(); @@ -117,15 +115,15 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { }); this.viewModeFormControl.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((holdingsViewMode) => { this.dataService .putUserSetting({ holdingsViewMode }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -149,11 +147,6 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchHoldings() { const filters = this.userService.getFilters(); @@ -193,7 +186,7 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { this.holdings = undefined; this.fetchHoldings() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.holdings = holdings; diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index ec131cd39..175c88606 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -46,7 +46,6 @@ }
(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { @@ -59,7 +57,7 @@ export class GfHomeMarketComponent implements OnDestroy, OnInit { this.info = this.dataService.fetchInfo(); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -82,7 +80,7 @@ export class GfHomeMarketComponent implements OnDestroy, OnInit { includeHistoricalData: this.numberOfDays, symbol: ghostfolioFearAndGreedIndexSymbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ historicalData, marketPrice }) => { this.fearAndGreedIndex = marketPrice; this.historicalDataItems = [ @@ -99,16 +97,11 @@ export class GfHomeMarketComponent implements OnDestroy, OnInit { this.dataService .fetchBenchmarks() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ benchmarks }) => { this.benchmarks = benchmarks; this.changeDetectorRef.markForCheck(); }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/home-overview/home-overview.component.ts b/apps/client/src/app/components/home-overview/home-overview.component.ts index 0d5020904..9b4f646c3 100644 --- a/apps/client/src/app/components/home-overview/home-overview.component.ts +++ b/apps/client/src/app/components/home-overview/home-overview.component.ts @@ -19,14 +19,13 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -41,7 +40,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-overview.scss'], templateUrl: './home-overview.html' }) -export class GfHomeOverviewComponent implements OnDestroy, OnInit { +export class GfHomeOverviewComponent implements OnInit { public deviceType: string; public errors: AssetProfileIdentifier[]; public hasError: boolean; @@ -57,30 +56,29 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { public routerLinkAccounts = internalRoutes.accounts.routerLink; public routerLinkPortfolio = internalRoutes.portfolio.routerLink; public routerLinkPortfolioActivities = - internalRoutes.portfolio.subRoutes.activities.routerLink; + internalRoutes.portfolio.subRoutes?.activities.routerLink; public showDetails = false; public unit: string; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private layoutService: LayoutService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; this.hasPermissionToCreateActivity = hasPermission( this.user.permissions, - permissions.createOrder + permissions.createActivity ); this.update(); @@ -99,7 +97,7 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; @@ -107,17 +105,12 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { }); this.layoutService.shouldReloadContent$ - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.update(); }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private update() { this.historicalDataItems = null; this.isLoadingPerformance = true; @@ -126,7 +119,7 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { .fetchPortfolioPerformance({ range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ chart, errors, performance }) => { this.errors = errors; this.performance = performance; diff --git a/apps/client/src/app/components/home-summary/home-summary.component.ts b/apps/client/src/app/components/home-summary/home-summary.component.ts index 454d05689..719cfbd29 100644 --- a/apps/client/src/app/components/home-summary/home-summary.component.ts +++ b/apps/client/src/app/components/home-summary/home-summary.component.ts @@ -13,14 +13,13 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [GfPortfolioSummaryComponent, MatCardModule], @@ -29,7 +28,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-summary.scss'], templateUrl: './home-summary.html' }) -export class GfHomeSummaryComponent implements OnDestroy, OnInit { +export class GfHomeSummaryComponent implements OnInit { public deviceType: string; public hasImpersonationId: boolean; public hasPermissionForSubscription: boolean; @@ -40,11 +39,10 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { public summary: PortfolioSummary; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private userService: UserService @@ -57,7 +55,7 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -77,7 +75,7 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); @@ -86,11 +84,11 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { public onChangeEmergencyFund(emergencyFund: number) { this.dataService .putUserSetting({ emergencyFund }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -99,17 +97,12 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private update() { this.isLoading = true; this.dataService .fetchPortfolioDetails() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ summary }) => { this.summary = summary; this.isLoading = false; diff --git a/apps/client/src/app/components/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index 4b83e897f..4214ee989 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -19,12 +19,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -39,7 +38,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./markets.scss'], templateUrl: './markets.html' }) -export class GfMarketsComponent implements OnDestroy, OnInit { +export class GfMarketsComponent implements OnInit { public benchmarks: Benchmark[]; public deviceType: string; public fearAndGreedIndex: number; @@ -55,18 +54,17 @@ export class GfMarketsComponent implements OnDestroy, OnInit { public readonly numberOfDays = 365; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -79,7 +77,7 @@ export class GfMarketsComponent implements OnDestroy, OnInit { public ngOnInit() { this.dataService .fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ fearAndGreedIndex }) => { this.fearAndGreedIndexData = fearAndGreedIndex; @@ -90,7 +88,7 @@ export class GfMarketsComponent implements OnDestroy, OnInit { this.dataService .fetchBenchmarks() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ benchmarks }) => { this.benchmarks = benchmarks; @@ -119,9 +117,4 @@ export class GfMarketsComponent implements OnDestroy, OnInit { this.initialize(); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index e2ffc1cf6..23f870ff3 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -9,11 +9,13 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, + DestroyRef, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -29,7 +31,6 @@ import { } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, takeUntil } from 'rxjs'; import { RuleSettingsDialogParams } from './rule-settings-dialog/interfaces/interfaces'; import { GfRuleSettingsDialogComponent } from './rule-settings-dialog/rule-settings-dialog.component'; @@ -58,9 +59,8 @@ export class GfRuleComponent implements OnInit { @Output() ruleUpdated = new EventEmitter(); private deviceType: string; - private unsubscribeSubject = new Subject(); - public constructor( + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog ) { @@ -94,7 +94,7 @@ export class GfRuleComponent implements OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((settings: RuleSettings) => { if (settings) { this.ruleUpdated.emit({ @@ -115,9 +115,4 @@ export class GfRuleComponent implements OnInit { this.ruleUpdated.emit(settings); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 ef78cccff..4f744a087 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,5 +1,4 @@ import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; -import { 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'; @@ -76,7 +75,6 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { private notificationService: NotificationService, private route: ActivatedRoute, private router: Router, - private tokenStorageService: TokenStorageService, private userService: UserService ) { const { globalPermissions } = this.dataService.fetchInfo(); @@ -161,8 +159,7 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { .subscribe(({ accessToken }) => { this.notificationService.alert({ discardFn: () => { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; }, 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 44be30b9a..0cf18df36 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 @@ -3,7 +3,6 @@ import { KEY_TOKEN, SettingsStorageService } 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 { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -108,7 +107,6 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { private notificationService: NotificationService, private settingsStorageService: SettingsStorageService, private snackBar: MatSnackBar, - private tokenStorageService: TokenStorageService, private userService: UserService, public webAuthnService: WebAuthnService ) { @@ -198,8 +196,7 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { takeUntil(this.unsubscribeSubject) ) .subscribe(() => { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; }); diff --git a/apps/client/src/app/core/auth.guard.ts b/apps/client/src/app/core/auth.guard.ts index 123a6169a..3292f0ff7 100644 --- a/apps/client/src/app/core/auth.guard.ts +++ b/apps/client/src/app/core/auth.guard.ts @@ -68,7 +68,7 @@ export class AuthGuard { this.dataService .putUserSetting({ language: document.documentElement.lang }) .subscribe(() => { - this.userService.remove(); + this.userService.reset(); setTimeout(() => { window.location.reload(); diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts index ab99b440f..315e9d64e 100644 --- a/apps/client/src/app/core/http-response.interceptor.ts +++ b/apps/client/src/app/core/http-response.interceptor.ts @@ -1,4 +1,4 @@ -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 { InfoItem } from '@ghostfolio/common/interfaces'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; @@ -32,8 +32,8 @@ export class HttpResponseInterceptor implements HttpInterceptor { public constructor( private dataService: DataService, private router: Router, - private tokenStorageService: TokenStorageService, private snackBar: MatSnackBar, + private userService: UserService, private webAuthnService: WebAuthnService ) { this.info = this.dataService.fetchInfo(); @@ -115,7 +115,7 @@ export class HttpResponseInterceptor implements HttpInterceptor { if (this.webAuthnService.isEnabled()) { this.router.navigate(internalRoutes.webauthn.routerLink); } else { - this.tokenStorageService.signOut(); + this.userService.signOut(); } } } diff --git a/apps/client/src/app/interfaces/interfaces.ts b/apps/client/src/app/interfaces/interfaces.ts new file mode 100644 index 000000000..493eff4ab --- /dev/null +++ b/apps/client/src/app/interfaces/interfaces.ts @@ -0,0 +1,8 @@ +import type { Params } from '@angular/router'; +import type { DataSource } from '@prisma/client'; + +export interface GfAppQueryParams extends Params { + dataSource?: DataSource; + holdingDetailDialog?: string; + symbol?: string; +} diff --git a/apps/client/src/app/pages/about/about-page.component.ts b/apps/client/src/app/pages/about/about-page.component.ts index 5ddb6b2e0..1e749d1cd 100644 --- a/apps/client/src/app/pages/about/about-page.component.ts +++ b/apps/client/src/app/pages/about/about-page.component.ts @@ -8,9 +8,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -24,8 +25,6 @@ import { sparklesOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -35,17 +34,16 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./about-page.scss'], templateUrl: './about-page.html' }) -export class AboutPageComponent implements OnDestroy, OnInit { +export class AboutPageComponent implements OnInit { public deviceType: string; public hasPermissionForSubscription: boolean; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { @@ -57,7 +55,7 @@ export class AboutPageComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { this.tabs = [ { @@ -118,9 +116,4 @@ export class AboutPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/about/about-page.routes.ts b/apps/client/src/app/pages/about/about-page.routes.ts index 4cb13280a..b4466fbab 100644 --- a/apps/client/src/app/pages/about/about-page.routes.ts +++ b/apps/client/src/app/pages/about/about-page.routes.ts @@ -15,29 +15,29 @@ export const routes: Routes = [ import('./overview/about-overview-page.routes').then((m) => m.routes) }, { - path: publicRoutes.about.subRoutes.changelog.path, + path: publicRoutes.about.subRoutes?.changelog.path, loadChildren: () => import('./changelog/changelog-page.routes').then((m) => m.routes) }, { - path: publicRoutes.about.subRoutes.license.path, + path: publicRoutes.about.subRoutes?.license.path, loadChildren: () => import('./license/license-page.routes').then((m) => m.routes) }, { - path: publicRoutes.about.subRoutes.ossFriends.path, + path: publicRoutes.about.subRoutes?.ossFriends.path, loadChildren: () => import('./oss-friends/oss-friends-page.routes').then((m) => m.routes) }, { - path: publicRoutes.about.subRoutes.privacyPolicy.path, + path: publicRoutes.about.subRoutes?.privacyPolicy.path, loadChildren: () => import('./privacy-policy/privacy-policy-page.routes').then( (m) => m.routes ) }, { - path: publicRoutes.about.subRoutes.termsOfService.path, + path: publicRoutes.about.subRoutes?.termsOfService.path, loadChildren: () => import('./terms-of-service/terms-of-service-page.routes').then( (m) => m.routes diff --git a/apps/client/src/app/pages/about/changelog/changelog-page.routes.ts b/apps/client/src/app/pages/about/changelog/changelog-page.routes.ts index 523218acc..7b67283e9 100644 --- a/apps/client/src/app/pages/about/changelog/changelog-page.routes.ts +++ b/apps/client/src/app/pages/about/changelog/changelog-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfChangelogPageComponent, path: '', - title: publicRoutes.about.subRoutes.changelog.title + title: publicRoutes.about.subRoutes?.changelog.title } ]; diff --git a/apps/client/src/app/pages/about/license/license-page.routes.ts b/apps/client/src/app/pages/about/license/license-page.routes.ts index 1684bb0c5..45de6aaa2 100644 --- a/apps/client/src/app/pages/about/license/license-page.routes.ts +++ b/apps/client/src/app/pages/about/license/license-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfLicensePageComponent, path: '', - title: publicRoutes.about.subRoutes.license.title + title: publicRoutes.about.subRoutes?.license.title } ]; diff --git a/apps/client/src/app/pages/about/oss-friends/oss-friends-page.routes.ts b/apps/client/src/app/pages/about/oss-friends/oss-friends-page.routes.ts index 8dbb9d52a..dfe65d962 100644 --- a/apps/client/src/app/pages/about/oss-friends/oss-friends-page.routes.ts +++ b/apps/client/src/app/pages/about/oss-friends/oss-friends-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfOpenSourceSoftwareFriendsPageComponent, path: '', - title: publicRoutes.about.subRoutes.ossFriends.title + title: publicRoutes.about.subRoutes?.ossFriends.title } ]; diff --git a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.routes.ts b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.routes.ts index e87436c17..934e06289 100644 --- a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.routes.ts +++ b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfPrivacyPolicyPageComponent, path: '', - title: publicRoutes.about.subRoutes.privacyPolicy.title + title: publicRoutes.about.subRoutes?.privacyPolicy.title } ]; diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts index dbf07ef19..7899f0187 100644 --- a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts +++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts @@ -1,6 +1,5 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule], @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./terms-of-service-page.scss'], templateUrl: './terms-of-service-page.html' }) -export class GfTermsOfServicePageComponent implements OnDestroy { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfTermsOfServicePageComponent {} diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.routes.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.routes.ts index 34d7a72d0..caf8751f1 100644 --- a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.routes.ts +++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfTermsOfServicePageComponent, path: '', - title: publicRoutes.about.subRoutes.termsOfService.title + title: publicRoutes.about.subRoutes?.termsOfService.title } ]; 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 f7e6541b5..fdc78a8c4 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -13,7 +13,13 @@ import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -21,8 +27,8 @@ import { Account as AccountModel } from '@prisma/client'; import { addIcons } from 'ionicons'; import { addOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { EMPTY, Subject, Subscription } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY, Subscription } from 'rxjs'; +import { catchError } 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'; @@ -36,7 +42,7 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba styleUrls: ['./accounts-page.scss'], templateUrl: './accounts-page.html' }) -export class GfAccountsPageComponent implements OnDestroy, OnInit { +export class GfAccountsPageComponent implements OnInit { public accounts: AccountModel[]; public activitiesCount = 0; public deviceType: string; @@ -48,11 +54,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { public totalValueInBaseCurrency = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, @@ -62,7 +67,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['accountId'] && params['accountDetailDialog']) { this.openAccountDetailDialog(params['accountId']); @@ -94,13 +99,13 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -124,7 +129,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { public fetchAccounts() { this.dataService .fetchAccounts() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ accounts, @@ -151,11 +156,11 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { this.dataService .deleteAccount(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchAccounts(); @@ -204,18 +209,18 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((account: UpdateAccountDto | null) => { if (account) { this.reset(); this.dataService .putAccount(account) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchAccounts(); @@ -228,11 +233,6 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private openAccountDetailDialog(aAccountId: string) { const dialogRef = this.dialog.open< GfAccountDetailDialogComponent, @@ -245,7 +245,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.createOrder) && + hasPermission(this.user?.permissions, permissions.createActivity) && !this.user?.settings?.isRestrictedView }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', @@ -254,7 +254,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchAccounts(); @@ -284,18 +284,18 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((account: CreateAccountDto | null) => { if (account) { this.reset(); this.dataService .postAccount(account) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchAccounts(); @@ -321,7 +321,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data: any) => { if (data) { this.reset(); @@ -343,7 +343,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.fetchAccounts(); 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 f4c68e70f..6cdd18251 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 @@ -5,12 +5,7 @@ import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { DataService } from '@ghostfolio/ui/services'; import { CommonModule, NgClass } from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { AbstractControl, FormBuilder, @@ -30,7 +25,7 @@ import { import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { Platform } from '@prisma/client'; -import { Observable, Subject } from 'rxjs'; +import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; @@ -55,14 +50,12 @@ import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-account-dialog.scss'], templateUrl: 'create-or-update-account-dialog.html' }) -export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { +export class GfCreateOrUpdateAccountDialogComponent { public accountForm: FormGroup; public currencies: string[] = []; public filteredPlatforms: Observable; public platforms: Platform[] = []; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccountDialogParams, private dataService: DataService, @@ -170,11 +163,6 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private autocompleteObjectValidator(): ValidatorFn { return (control: AbstractControl) => { if (control.value && typeof control.value === 'string') { 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 85d2e60bf..34a66b156 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,12 +1,7 @@ import { TransferBalanceDto } from '@ghostfolio/common/dtos'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { AbstractControl, FormBuilder, @@ -25,7 +20,6 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { Account } from '@prisma/client'; -import { Subject } from 'rxjs'; import { TransferBalanceDialogParams } from './interfaces/interfaces'; @@ -45,13 +39,11 @@ import { TransferBalanceDialogParams } from './interfaces/interfaces'; styleUrls: ['./transfer-balance-dialog.scss'], templateUrl: 'transfer-balance-dialog.html' }) -export class GfTransferBalanceDialogComponent implements OnDestroy { +export class GfTransferBalanceDialogComponent { public accounts: Account[] = []; public currency: string; public transferBalanceForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: TransferBalanceDialogParams, public dialogRef: MatDialogRef, @@ -93,11 +85,6 @@ export class GfTransferBalanceDialogComponent implements OnDestroy { this.dialogRef.close({ account }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private compareAccounts(control: AbstractControl): ValidationErrors { const accountFrom = control.get('fromAccount'); const accountTo = control.get('toAccount'); diff --git a/apps/client/src/app/pages/admin/admin-page.routes.ts b/apps/client/src/app/pages/admin/admin-page.routes.ts index c5309edbb..08fce248c 100644 --- a/apps/client/src/app/pages/admin/admin-page.routes.ts +++ b/apps/client/src/app/pages/admin/admin-page.routes.ts @@ -20,29 +20,29 @@ export const routes: Routes = [ title: internalRoutes.adminControl.title }, { - path: internalRoutes.adminControl.subRoutes.jobs.path, + path: internalRoutes.adminControl.subRoutes?.jobs.path, component: GfAdminJobsComponent, - title: internalRoutes.adminControl.subRoutes.jobs.title + title: internalRoutes.adminControl.subRoutes?.jobs.title }, { - path: internalRoutes.adminControl.subRoutes.marketData.path, + path: internalRoutes.adminControl.subRoutes?.marketData.path, component: GfAdminMarketDataComponent, - title: internalRoutes.adminControl.subRoutes.marketData.title + title: internalRoutes.adminControl.subRoutes?.marketData.title }, { - path: internalRoutes.adminControl.subRoutes.settings.path, + path: internalRoutes.adminControl.subRoutes?.settings.path, component: GfAdminSettingsComponent, - title: internalRoutes.adminControl.subRoutes.settings.title + title: internalRoutes.adminControl.subRoutes?.settings.title }, { - path: internalRoutes.adminControl.subRoutes.users.path, + path: internalRoutes.adminControl.subRoutes?.users.path, component: GfAdminUsersComponent, - title: internalRoutes.adminControl.subRoutes.users.title + title: internalRoutes.adminControl.subRoutes?.users.title }, { - path: `${internalRoutes.adminControl.subRoutes.users.path}/:userId`, + path: `${internalRoutes.adminControl.subRoutes?.users.path}/:userId`, component: GfAdminUsersComponent, - title: internalRoutes.adminControl.subRoutes.users.title + title: internalRoutes.adminControl.subRoutes?.users.title } ], component: AdminPageComponent, diff --git a/apps/client/src/app/pages/auth/auth-page.component.ts b/apps/client/src/app/pages/auth/auth-page.component.ts index 082401d6d..1b0b4a67c 100644 --- a/apps/client/src/app/pages/auth/auth-page.component.ts +++ b/apps/client/src/app/pages/auth/auth-page.component.ts @@ -4,20 +4,18 @@ import { } from '@ghostfolio/client/services/settings-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-auth-page', styleUrls: ['./auth-page.scss'], templateUrl: './auth-page.html' }) -export class GfAuthPageComponent implements OnDestroy, OnInit { - private unsubscribeSubject = new Subject(); - +export class GfAuthPageComponent implements OnInit { public constructor( + private destroyRef: DestroyRef, private route: ActivatedRoute, private router: Router, private settingsStorageService: SettingsStorageService, @@ -26,7 +24,7 @@ export class GfAuthPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.route.params - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { const jwt = params['jwt']; @@ -38,9 +36,4 @@ export class GfAuthPageComponent implements OnDestroy, OnInit { this.router.navigate(['/']); }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts b/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts index c5a9cf178..4b62e4449 100644 --- a/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts +++ b/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.component.ts @@ -12,6 +12,6 @@ import { RouterModule } from '@angular/router'; }) export class GhostfolioJoinsOssFriendsPageComponent { public routerLinkAboutOssFriends = - publicRoutes.about.subRoutes.ossFriends.routerLink; + publicRoutes.about.subRoutes?.ossFriends.routerLink; public routerLinkBlog = publicRoutes.blog.routerLink; } diff --git a/apps/client/src/app/pages/faq/faq-page.component.ts b/apps/client/src/app/pages/faq/faq-page.component.ts index caf4217ca..83171254a 100644 --- a/apps/client/src/app/pages/faq/faq-page.component.ts +++ b/apps/client/src/app/pages/faq/faq-page.component.ts @@ -3,19 +3,13 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { DataService } from '@ghostfolio/ui/services'; -import { - CUSTOM_ELEMENTS_SCHEMA, - Component, - OnDestroy, - OnInit -} from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, Component, OnInit } from '@angular/core'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { cloudyOutline, readerOutline, serverOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -25,13 +19,11 @@ import { Subject } from 'rxjs'; styleUrls: ['./faq-page.scss'], templateUrl: './faq-page.html' }) -export class GfFaqPageComponent implements OnDestroy, OnInit { +export class GfFaqPageComponent implements OnInit { public deviceType: string; public hasPermissionForSubscription: boolean; public tabs: TabConfiguration[] = []; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, private deviceService: DeviceDetectorService @@ -68,9 +60,4 @@ export class GfFaqPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/faq/faq-page.routes.ts b/apps/client/src/app/pages/faq/faq-page.routes.ts index 2999e3c80..f80304a08 100644 --- a/apps/client/src/app/pages/faq/faq-page.routes.ts +++ b/apps/client/src/app/pages/faq/faq-page.routes.ts @@ -15,12 +15,12 @@ export const routes: Routes = [ import('./overview/faq-overview-page.routes').then((m) => m.routes) }, { - path: publicRoutes.faq.subRoutes.saas.path, + path: publicRoutes.faq.subRoutes?.saas.path, loadChildren: () => import('./saas/saas-page.routes').then((m) => m.routes) }, { - path: publicRoutes.faq.subRoutes.selfHosting.path, + path: publicRoutes.faq.subRoutes?.selfHosting.path, loadChildren: () => import('./self-hosting/self-hosting-page.routes').then( (m) => m.routes diff --git a/apps/client/src/app/pages/faq/saas/saas-page.component.ts b/apps/client/src/app/pages/faq/saas/saas-page.component.ts index b47d45fe2..4429fe492 100644 --- a/apps/client/src/app/pages/faq/saas/saas-page.component.ts +++ b/apps/client/src/app/pages/faq/saas/saas-page.component.ts @@ -25,7 +25,7 @@ export class GfSaasPageComponent implements OnDestroy { public pricingUrl = `https://ghostfol.io/${document.documentElement.lang}/${publicRoutes.pricing.path}`; public routerLinkAccount = internalRoutes.account.routerLink; public routerLinkAccountMembership = - internalRoutes.account.subRoutes.membership.routerLink; + internalRoutes.account.subRoutes?.membership.routerLink; public routerLinkMarkets = publicRoutes.markets.routerLink; public routerLinkRegister = publicRoutes.register.routerLink; public user: User; diff --git a/apps/client/src/app/pages/faq/saas/saas-page.routes.ts b/apps/client/src/app/pages/faq/saas/saas-page.routes.ts index dcb574c38..d68893640 100644 --- a/apps/client/src/app/pages/faq/saas/saas-page.routes.ts +++ b/apps/client/src/app/pages/faq/saas/saas-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfSaasPageComponent, path: '', - title: `${publicRoutes.faq.subRoutes.saas.title} - ${publicRoutes.faq.title}` + title: `${publicRoutes.faq.subRoutes?.saas.title} - ${publicRoutes.faq.title}` } ]; diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts index ed1d74395..f2468c7d4 100644 --- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts +++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts @@ -1,10 +1,9 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { CUSTOM_ELEMENTS_SCHEMA, Component, OnDestroy } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -14,13 +13,6 @@ import { Subject } from 'rxjs'; styleUrls: ['./self-hosting-page.scss'], templateUrl: './self-hosting-page.html' }) -export class GfSelfHostingPageComponent implements OnDestroy { +export class GfSelfHostingPageComponent { public pricingUrl = `https://ghostfol.io/${document.documentElement.lang}/${publicRoutes.pricing.path}`; - - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.routes.ts b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.routes.ts index d08cdb312..ccf034421 100644 --- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.routes.ts +++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfSelfHostingPageComponent, path: '', - title: `${publicRoutes.faq.subRoutes.selfHosting.title} - ${publicRoutes.faq.title}` + title: `${publicRoutes.faq.subRoutes?.selfHosting.title} - ${publicRoutes.faq.title}` } ]; diff --git a/apps/client/src/app/pages/home/home-page.component.ts b/apps/client/src/app/pages/home/home-page.component.ts index fd860ced5..5130c8166 100644 --- a/apps/client/src/app/pages/home/home-page.component.ts +++ b/apps/client/src/app/pages/home/home-page.component.ts @@ -8,9 +8,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -23,8 +24,6 @@ import { readerOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -34,22 +33,21 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-page.scss'], templateUrl: './home-page.html' }) -export class GfHomePageComponent implements OnDestroy, OnInit { +export class GfHomePageComponent implements OnInit { public deviceType: string; public hasImpersonationId: boolean; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -110,14 +108,9 @@ export class GfHomePageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/home/home-page.routes.ts b/apps/client/src/app/pages/home/home-page.routes.ts index 82ef1e521..436826674 100644 --- a/apps/client/src/app/pages/home/home-page.routes.ts +++ b/apps/client/src/app/pages/home/home-page.routes.ts @@ -20,29 +20,29 @@ export const routes: Routes = [ component: GfHomeOverviewComponent }, { - path: internalRoutes.home.subRoutes.holdings.path, + path: internalRoutes.home.subRoutes?.holdings.path, component: GfHomeHoldingsComponent, - title: internalRoutes.home.subRoutes.holdings.title + title: internalRoutes.home.subRoutes?.holdings.title }, { - path: internalRoutes.home.subRoutes.summary.path, + path: internalRoutes.home.subRoutes?.summary.path, component: GfHomeSummaryComponent, - title: internalRoutes.home.subRoutes.summary.title + title: internalRoutes.home.subRoutes?.summary.title }, { - path: internalRoutes.home.subRoutes.markets.path, + path: internalRoutes.home.subRoutes?.markets.path, component: GfHomeMarketComponent, - title: internalRoutes.home.subRoutes.markets.title + title: internalRoutes.home.subRoutes?.markets.title }, { - path: internalRoutes.home.subRoutes.marketsPremium.path, + path: internalRoutes.home.subRoutes?.marketsPremium.path, component: GfMarketsComponent, - title: internalRoutes.home.subRoutes.marketsPremium.title + title: internalRoutes.home.subRoutes?.marketsPremium.title }, { - path: internalRoutes.home.subRoutes.watchlist.path, + path: internalRoutes.home.subRoutes?.watchlist.path, component: GfHomeWatchlistComponent, - title: internalRoutes.home.subRoutes.watchlist.title + title: internalRoutes.home.subRoutes?.watchlist.title } ], component: GfHomePageComponent, diff --git a/apps/client/src/app/pages/i18n/i18n-page.component.ts b/apps/client/src/app/pages/i18n/i18n-page.component.ts index 19ecd222e..76d123914 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.component.ts +++ b/apps/client/src/app/pages/i18n/i18n-page.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./i18n-page.scss'], templateUrl: './i18n-page.html' }) -export class GfI18nPageComponent { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfI18nPageComponent {} diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index 25fb2d6e7..9ee9cceb4 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -9,7 +9,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { CommonModule } from '@angular/common'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @@ -21,7 +21,6 @@ import { starOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -40,7 +39,7 @@ import { Subject } from 'rxjs'; styleUrls: ['./landing-page.scss'], templateUrl: './landing-page.html' }) -export class GfLandingPageComponent implements OnDestroy, OnInit { +export class GfLandingPageComponent implements OnInit { public countriesOfSubscribersMap: { [code: string]: { value: number }; } = {}; @@ -107,8 +106,6 @@ export class GfLandingPageComponent implements OnDestroy, OnInit { } ]; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, private deviceService: DeviceDetectorService @@ -155,9 +152,4 @@ export class GfLandingPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/open/open-page.component.ts b/apps/client/src/app/pages/open/open-page.component.ts index 6284c41f4..090588d7d 100644 --- a/apps/client/src/app/pages/open/open-page.component.ts +++ b/apps/client/src/app/pages/open/open-page.component.ts @@ -7,11 +7,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -21,15 +21,14 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./open-page.scss'], templateUrl: './open-page.html' }) -export class GfOpenPageComponent implements OnDestroy, OnInit { +export class GfOpenPageComponent implements OnInit { public statistics: Statistics; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private userService: UserService ) { const { statistics } = this.dataService.fetchInfo(); @@ -39,7 +38,7 @@ export class GfOpenPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -48,9 +47,4 @@ export class GfOpenPageComponent implements OnDestroy, OnInit { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 cf7a41215..4e5daf345 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 @@ -10,6 +10,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { DateRange } from '@ghostfolio/common/types'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { DataService } from '@ghostfolio/ui/services'; @@ -129,8 +130,13 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { } public fetchActivities() { + const dateRange = this.user?.settings?.dateRange; + + const range = this.isCalendarYear(dateRange) ? dateRange : undefined; + this.dataService .fetchActivities({ + range, filters: this.userService.getFilters(), skip: this.pageIndex * this.pageSize, sortColumn: this.sortColumn, @@ -334,7 +340,7 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { .subscribe((activity: UpdateOrderDto) => { if (activity) { this.dataService - .putOrder(activity) + .putActivity(activity) .pipe(takeUntil(this.unsubscribeSubject)) .subscribe({ next: () => { @@ -352,6 +358,14 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } + private isCalendarYear(dateRange: DateRange) { + if (!dateRange) { + return false; + } + + return /^\d{4}$/.test(dateRange); + } + private openCreateActivityDialog(aActivity?: Activity) { this.userService .get() @@ -385,7 +399,7 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((transaction: CreateOrderDto | null) => { if (transaction) { - this.dataService.postOrder(transaction).subscribe({ + this.dataService.postActivity(transaction).subscribe({ next: () => { this.userService .get(true) @@ -407,9 +421,9 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { this.hasPermissionToCreateActivity = !this.hasImpersonationId && - hasPermission(this.user.permissions, permissions.createOrder); + hasPermission(this.user.permissions, permissions.createActivity); this.hasPermissionToDeleteActivity = !this.hasImpersonationId && - hasPermission(this.user.permissions, permissions.deleteOrder); + hasPermission(this.user.permissions, permissions.deleteActivity); } } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.routes.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.routes.ts index c96c8a558..e1c75f6cc 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.routes.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfActivitiesPageComponent, path: '', - title: internalRoutes.portfolio.subRoutes.activities.title + title: internalRoutes.portfolio.subRoutes?.activities.title } ]; 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 c7cd63191..9cc312b25 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -20,9 +20,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - Inject, - OnDestroy + DestroyRef, + Inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, @@ -46,8 +47,8 @@ import { AssetClass, Tag, Type } from '@prisma/client'; import { isAfter, isToday } from 'date-fns'; import { addIcons } from 'ionicons'; import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, delay, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError, delay } from 'rxjs/operators'; import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; import { ActivityType } from './types/activity-type.type'; @@ -75,7 +76,7 @@ import { ActivityType } from './types/activity-type.type'; styleUrls: ['./create-or-update-activity-dialog.scss'], templateUrl: 'create-or-update-activity-dialog.html' }) -export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { +export class GfCreateOrUpdateActivityDialogComponent { public activityForm: FormGroup; public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass) @@ -101,13 +102,12 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { public typesTranslationMap = new Map(); public Validators = Validators; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams, private dataService: DataService, private dateAdapter: DateAdapter, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private formBuilder: FormBuilder, @Inject(MAT_DATE_LOCALE) private locale: string, @@ -133,7 +133,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.dataService .fetchPortfolioHoldings() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.defaultLookupItems = holdings .filter(({ assetSubClass }) => { @@ -237,7 +237,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { // Slightly delay until the more specific form control value changes have // completed delay(300), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(async () => { if ( @@ -284,7 +284,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.activityForm .get('assetClass') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((assetClass) => { const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; @@ -335,7 +335,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { if (newTag && this.hasPermissionToCreateOwnTag) { this.dataService .postTag({ ...newTag, userId: this.data.user.id }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag) => { this.activityForm.get('tags').setValue( tags.map((currentTag) => { @@ -349,7 +349,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); }); } @@ -357,7 +357,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.activityForm .get('type') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((type: ActivityType) => { if ( type === 'VALUABLE' || @@ -465,7 +465,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { dataSource: this.data.activity?.SymbolProfile?.dataSource, symbol: this.data.activity?.SymbolProfile?.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketPrice }) => { this.currentMarketPrice = marketPrice; @@ -557,11 +557,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private updateAssetProfile() { this.isLoading = true; this.changeDetectorRef.markForCheck(); @@ -581,7 +576,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ currency, dataSource, marketPrice }) => { if (this.mode === 'create') { 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 1a84e9f31..00f0508fe 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 @@ -21,9 +21,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - Inject, - OnDestroy + DestroyRef, + Inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, @@ -52,7 +53,6 @@ import { cloudUploadOutline, warningOutline } from 'ionicons/icons'; import { isArray, sortBy } from 'lodash'; import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; import { ImportStep } from './enums/import-step'; import { ImportActivitiesDialogParams } from './interfaces/interfaces'; @@ -81,7 +81,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; styleUrls: ['./import-activities-dialog.scss'], templateUrl: 'import-activities-dialog.html' }) -export class GfImportActivitiesDialogComponent implements OnDestroy { +export class GfImportActivitiesDialogComponent { public accounts: CreateAccountWithBalancesDto[] = []; public activities: Activity[] = []; public assetProfileForm: FormGroup; @@ -104,12 +104,11 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { public tags: CreateTagDto[] = []; public totalItems: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private formBuilder: FormBuilder, public dialogRef: MatDialogRef, @@ -152,7 +151,7 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { ], range: 'max' }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.holdings = sortBy(holdings, ({ name }) => { return name.toLowerCase(); @@ -237,7 +236,7 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities }) => { this.activities = activities; this.dataSource = new MatTableDataSource(activities.reverse()); @@ -284,11 +283,6 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private async handleFile({ file, stepper 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 70fa09eb1..367716d2d 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -22,7 +22,13 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { NgClass } from '@angular/common'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; @@ -36,8 +42,6 @@ import { } from '@prisma/client'; import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -54,7 +58,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./allocations-page.scss'], templateUrl: './allocations-page.html' }) -export class GfAllocationsPageComponent implements OnDestroy, OnInit { +export class GfAllocationsPageComponent implements OnInit { public accounts: { [id: string]: Pick & { id: string; @@ -119,11 +123,10 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { public user: User; public worldMapChartFormat: string; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, @@ -132,7 +135,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['accountId'] && params['accountDetailDialog']) { this.openAccountDetailDialog(params['accountId']); @@ -145,13 +148,13 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -166,7 +169,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { this.initialize(); this.fetchPortfolioDetails() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((portfolioDetails) => { this.initialize(); @@ -202,11 +205,6 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private extractEtfProvider({ assetSubClass, name @@ -407,17 +405,22 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } if (position.holdings.length > 0) { - for (const holding of position.holdings) { - const { allocationInPercentage, name, valueInBaseCurrency } = - holding; - - if (this.topHoldingsMap[name]?.value) { - this.topHoldingsMap[name].value += isNumber(valueInBaseCurrency) + for (const { + allocationInPercentage, + name, + valueInBaseCurrency + } of position.holdings) { + const normalizedAssetName = this.normalizeAssetName(name); + + if (this.topHoldingsMap[normalizedAssetName]?.value) { + this.topHoldingsMap[normalizedAssetName].value += isNumber( + valueInBaseCurrency + ) ? valueInBaseCurrency : allocationInPercentage * this.portfolioDetails.holdings[symbol].valueInPercentage; } else { - this.topHoldingsMap[name] = { + this.topHoldingsMap[normalizedAssetName] = { name, value: isNumber(valueInBaseCurrency) ? valueInBaseCurrency @@ -520,7 +523,10 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { if (holding.holdings.length > 0) { const currentParentHolding = holding.holdings.find( (parentHolding) => { - return parentHolding.name === name; + return ( + this.normalizeAssetName(parentHolding.name) === + this.normalizeAssetName(name) + ); } ); @@ -557,6 +563,14 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } } + private normalizeAssetName(name: string) { + if (!name) { + return ''; + } + + return name.trim().toLowerCase(); + } + private openAccountDetailDialog(aAccountId: string) { const dialogRef = this.dialog.open< GfAccountDetailDialogComponent, @@ -569,7 +583,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.createOrder) && + hasPermission(this.user?.permissions, permissions.createActivity) && !this.user?.settings?.isRestrictedView }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', @@ -578,7 +592,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['.'], { relativeTo: this.route }); }); diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 5cd24777c..47845ea6f 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -24,10 +24,11 @@ import { Clipboard } from '@angular/cdk/clipboard'; import { ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; @@ -42,8 +43,6 @@ import { isNumber, sortBy } from 'lodash'; import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -64,7 +63,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./analysis-page.scss'], templateUrl: './analysis-page.html' }) -export class GfAnalysisPageComponent implements OnDestroy, OnInit { +export class GfAnalysisPageComponent implements OnInit { @ViewChild(MatMenuTrigger) actionsMenuButton!: MatMenuTrigger; public benchmark: Partial; @@ -102,12 +101,11 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { public unitLongestStreak: string; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private clipboard: Clipboard, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private snackBar: MatSnackBar, @@ -135,13 +133,13 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -163,11 +161,11 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { public onChangeBenchmark(symbolProfileId: string) { this.dataService .putUserSetting({ benchmark: symbolProfileId }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -193,7 +191,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { mode, filters: this.userService.getFilters() }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ prompt }) => { this.clipboard.copy(prompt); @@ -207,7 +205,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { snackBarRef .onAction() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { window.open('https://duck.ai', '_blank'); }); @@ -222,11 +220,6 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchDividendsAndInvestments() { this.isLoadingDividendTimelineChart = true; this.isLoadingInvestmentTimelineChart = true; @@ -237,7 +230,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { groupBy: this.mode, range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dividends }) => { this.dividendsByGroup = dividends; @@ -252,7 +245,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { groupBy: this.mode, range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ investments, streaks }) => { this.investmentsByGroup = investments; this.streaks = streaks; @@ -287,7 +280,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { filters: this.userService.getFilters(), range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ chart, firstOrderDate, performance }) => { this.firstOrderDate = firstOrderDate ?? new Date(); @@ -346,7 +339,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { filters: this.userService.getFilters(), range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { const holdingsSorted = sortBy( holdings.filter(({ netPerformancePercentWithCurrencyEffect }) => { @@ -397,7 +390,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { range: this.user?.settings?.dateRange, startDate: this.firstOrderDate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketData }) => { this.benchmarkDataItems = marketData.map(({ date, value }) => { return { diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.routes.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.routes.ts index 91cf4c91b..343d6ced5 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.routes.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.routes.ts @@ -10,6 +10,6 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: GfAnalysisPageComponent, path: '', - title: internalRoutes.portfolio.subRoutes.analysis.title + title: internalRoutes.portfolio.subRoutes?.analysis.title } ]; diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 27db6c76e..2f7568982 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -12,14 +12,18 @@ import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule, NgStyle } from '@angular/common'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormControl } from '@angular/forms'; import { Big } from 'big.js'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -36,7 +40,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./fire-page.scss'], templateUrl: './fire-page.html' }) -export class GfFirePageComponent implements OnDestroy, OnInit { +export class GfFirePageComponent implements OnInit { public deviceType: string; public fireWealth: FireWealth; public hasImpersonationId: boolean; @@ -52,11 +56,10 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public withdrawalRatePerYear: Big; public withdrawalRatePerYearProjected: Big; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private userService: UserService @@ -68,7 +71,7 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.dataService .fetchPortfolioDetails() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ summary }) => { this.fireWealth = { today: { @@ -92,19 +95,19 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.safeWithdrawalRateControl.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((value) => { this.onSafeWithdrawalRateChange(Number(value)); }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -132,11 +135,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public onAnnualInterestRateChange(annualInterestRate: number) { this.dataService .putUserSetting({ annualInterestRate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -163,11 +166,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { retirementDate: retirementDate.toISOString(), projectedTotalAmount: null }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -179,11 +182,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public onSafeWithdrawalRateChange(safeWithdrawalRate: number) { this.dataService .putUserSetting({ safeWithdrawalRate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -198,11 +201,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public onSavingsRateChange(savingsRate: number) { this.dataService .putUserSetting({ savingsRate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -217,11 +220,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { projectedTotalAmount, retirementDate: null }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -230,11 +233,6 @@ export class GfFirePageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private calculateWithdrawalRates() { if (this.fireWealth && this.user?.settings?.safeWithdrawalRate) { this.withdrawalRatePerYear = new Big( diff --git a/apps/client/src/app/pages/portfolio/portfolio-page.routes.ts b/apps/client/src/app/pages/portfolio/portfolio-page.routes.ts index aeebf4a6b..a4fdc3f61 100644 --- a/apps/client/src/app/pages/portfolio/portfolio-page.routes.ts +++ b/apps/client/src/app/pages/portfolio/portfolio-page.routes.ts @@ -15,22 +15,22 @@ export const routes: Routes = [ import('./analysis/analysis-page.routes').then((m) => m.routes) }, { - path: internalRoutes.portfolio.subRoutes.activities.path, + path: internalRoutes.portfolio.subRoutes?.activities.path, loadChildren: () => import('./activities/activities-page.routes').then((m) => m.routes) }, { - path: internalRoutes.portfolio.subRoutes.allocations.path, + path: internalRoutes.portfolio.subRoutes?.allocations.path, loadChildren: () => import('./allocations/allocations-page.routes').then((m) => m.routes) }, { - path: internalRoutes.portfolio.subRoutes.fire.path, + path: internalRoutes.portfolio.subRoutes?.fire.path, loadChildren: () => import('./fire/fire-page.routes').then((m) => m.routes) }, { - path: internalRoutes.portfolio.subRoutes.xRay.path, + path: internalRoutes.portfolio.subRoutes?.xRay.path, loadChildren: () => import('./x-ray/x-ray-page.routes').then((m) => m.routes) } 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 70b748b10..e97fd4876 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -12,7 +12,8 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; import { NgClass } from '@angular/common'; -import { ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { @@ -21,7 +22,6 @@ import { warningOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, takeUntil } from 'rxjs'; @Component({ imports: [ @@ -48,11 +48,10 @@ export class GfXRayPageComponent { public statistics: PortfolioReportResponse['xRay']['statistics']; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private impersonationStorageService: ImpersonationStorageService, private userService: UserService ) { @@ -62,13 +61,13 @@ export class GfXRayPageComponent { public ngOnInit() { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -91,28 +90,23 @@ export class GfXRayPageComponent { public onRulesUpdated(event: UpdateUserSettingDto) { this.dataService .putUserSetting(event) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.initializePortfolioReport(); }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initializePortfolioReport() { this.isLoading = true; this.dataService .fetchPortfolioReport() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ xRay: { categories, statistics } }) => { this.categories = categories; this.inactiveRules = this.mergeInactiveRules(categories); diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index fe4b295db..9ccbbf52e 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -19,8 +19,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatTableDataSource } from '@angular/material/table'; @@ -29,8 +31,8 @@ import { AssetClass } from '@prisma/client'; import { StatusCodes } from 'http-status-codes'; import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ host: { class: 'page' }, @@ -83,12 +85,12 @@ export class GfPublicPageComponent implements OnInit { public UNKNOWN_KEY = UNKNOWN_KEY; private accessId: string; - private unsubscribeSubject = new Subject(); public constructor( private activatedRoute: ActivatedRoute, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private router: Router ) { @@ -110,7 +112,7 @@ export class GfPublicPageComponent implements OnInit { this.dataService .fetchPublicPortfolio(this.accessId) .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), catchError((error) => { if (error.status === StatusCodes.NOT_FOUND) { console.error(error); @@ -246,9 +248,4 @@ export class GfPublicPageComponent implements OnInit { }; } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 3678f0249..db199143f 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -1,4 +1,5 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { InfoItem, LineChartItem } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfLogoComponent } from '@ghostfolio/ui/logo'; @@ -42,11 +43,12 @@ export class GfRegisterPageComponent implements OnInit { private deviceService: DeviceDetectorService, private dialog: MatDialog, private router: Router, - private tokenStorageService: TokenStorageService + private tokenStorageService: TokenStorageService, + private userService: UserService ) { this.info = this.dataService.fetchInfo(); - this.tokenStorageService.signOut(); + this.userService.signOut(); } public ngOnInit() { diff --git a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts index 4cc0f52f8..cbbe2d29c 100644 --- a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts +++ b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts @@ -8,10 +8,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, Inject, - OnDestroy, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -27,8 +28,6 @@ import { checkmarkOutline, copyOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { UserAccountRegistrationDialogParams } from './interfaces/interfaces'; @@ -53,7 +52,7 @@ import { UserAccountRegistrationDialogParams } from './interfaces/interfaces'; styleUrls: ['./user-account-registration-dialog.scss'], templateUrl: 'user-account-registration-dialog.html' }) -export class GfUserAccountRegistrationDialogComponent implements OnDestroy { +export class GfUserAccountRegistrationDialogComponent { @ViewChild(MatStepper) stepper!: MatStepper; public accessToken: string; @@ -64,12 +63,11 @@ export class GfUserAccountRegistrationDialogComponent implements OnDestroy { public routerLinkAboutTermsOfService = publicRoutes.about.subRoutes.termsOfService.routerLink; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: UserAccountRegistrationDialogParams, - private dataService: DataService + private dataService: DataService, + private destroyRef: DestroyRef ) { addIcons({ arrowForwardOutline, checkmarkOutline, copyOutline }); } @@ -77,7 +75,7 @@ export class GfUserAccountRegistrationDialogComponent implements OnDestroy { public createAccount() { this.dataService .postUser() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ accessToken, authToken, role }) => { this.accessToken = accessToken; this.authToken = authToken; @@ -96,9 +94,4 @@ export class GfUserAccountRegistrationDialogComponent implements OnDestroy { public onChangeDislaimerChecked() { this.isDisclaimerChecked = !this.isDisclaimerChecked; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts index 112619239..9c86f2c83 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts @@ -16,7 +16,7 @@ export class ResourcesGlossaryPageComponent implements OnInit { public hasPermissionForSubscription: boolean; public info: InfoItem; public routerLinkResourcesPersonalFinanceTools = - publicRoutes.resources.subRoutes.personalFinanceTools.routerLink; + publicRoutes.resources.subRoutes?.personalFinanceTools.routerLink; public constructor(private dataService: DataService) { this.info = this.dataService.fetchInfo(); diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.routes.ts b/apps/client/src/app/pages/resources/glossary/resources-glossary.routes.ts index 2f864155a..4096dfecd 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.routes.ts +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.routes.ts @@ -8,6 +8,6 @@ export const routes: Routes = [ { component: ResourcesGlossaryPageComponent, path: '', - title: publicRoutes.resources.subRoutes.glossary.title + title: publicRoutes.resources.subRoutes?.glossary.title } ]; diff --git a/apps/client/src/app/pages/resources/guides/resources-guides.routes.ts b/apps/client/src/app/pages/resources/guides/resources-guides.routes.ts index f9f65f44a..eb8c39e24 100644 --- a/apps/client/src/app/pages/resources/guides/resources-guides.routes.ts +++ b/apps/client/src/app/pages/resources/guides/resources-guides.routes.ts @@ -8,6 +8,6 @@ export const routes: Routes = [ { component: ResourcesGuidesComponent, path: '', - title: publicRoutes.resources.subRoutes.guides.title + title: publicRoutes.resources.subRoutes?.guides.title } ]; diff --git a/apps/client/src/app/pages/resources/markets/resources-markets.routes.ts b/apps/client/src/app/pages/resources/markets/resources-markets.routes.ts index 4bcb66789..a56dc7cf7 100644 --- a/apps/client/src/app/pages/resources/markets/resources-markets.routes.ts +++ b/apps/client/src/app/pages/resources/markets/resources-markets.routes.ts @@ -8,6 +8,6 @@ export const routes: Routes = [ { component: ResourcesMarketsComponent, path: '', - title: publicRoutes.resources.subRoutes.markets.title + title: publicRoutes.resources.subRoutes?.markets.title } ]; 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 3a6f18d40..86a334c79 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 @@ -7,7 +7,9 @@

{{ item.title }}

{{ item.description }}

- Explore {{ item.title }} → + Explore {{ item.title }}
}
diff --git a/apps/client/src/app/pages/resources/overview/resources-overview.component.ts b/apps/client/src/app/pages/resources/overview/resources-overview.component.ts index 81338200f..310f16a5e 100644 --- a/apps/client/src/app/pages/resources/overview/resources-overview.component.ts +++ b/apps/client/src/app/pages/resources/overview/resources-overview.component.ts @@ -20,20 +20,20 @@ export class ResourcesOverviewComponent { { description: 'Explore our guides to help you get started with investing and managing your finances.', - routerLink: publicRoutes.resources.subRoutes.guides.routerLink, - title: publicRoutes.resources.subRoutes.guides.title + routerLink: publicRoutes.resources.subRoutes?.guides.routerLink, + title: publicRoutes.resources.subRoutes?.guides.title }, { description: 'Access various market resources and tools to stay informed about financial markets.', - routerLink: publicRoutes.resources.subRoutes.markets.routerLink, - title: publicRoutes.resources.subRoutes.markets.title + routerLink: publicRoutes.resources.subRoutes?.markets.routerLink, + title: publicRoutes.resources.subRoutes?.markets.title }, { description: 'Learn key financial terms and concepts in our comprehensive glossary.', - routerLink: publicRoutes.resources.subRoutes.glossary.routerLink, - title: publicRoutes.resources.subRoutes.glossary.title + routerLink: publicRoutes.resources.subRoutes?.glossary.routerLink, + title: publicRoutes.resources.subRoutes?.glossary.title } ]; } diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts index c4cfd184e..bb4ae3889 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts @@ -1,13 +1,12 @@ import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { chevronForwardOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -16,7 +15,7 @@ import { Subject } from 'rxjs'; styleUrls: ['./personal-finance-tools-page.scss'], templateUrl: './personal-finance-tools-page.html' }) -export class PersonalFinanceToolsPageComponent implements OnDestroy { +export class PersonalFinanceToolsPageComponent { public pathAlternativeTo = publicRoutes.resources.subRoutes.personalFinanceTools.subRoutes.product .path + '-'; @@ -28,14 +27,7 @@ export class PersonalFinanceToolsPageComponent implements OnDestroy { }); public routerLinkAbout = publicRoutes.about.routerLink; - private unsubscribeSubject = new Subject(); - public constructor() { addIcons({ chevronForwardOutline }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.routes.ts b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.routes.ts index 9081f6b28..081c34000 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.routes.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.routes.ts @@ -11,7 +11,7 @@ export const routes: Routes = [ canActivate: [AuthGuard], component: PersonalFinanceToolsPageComponent, path: '', - title: publicRoutes.resources.subRoutes.personalFinanceTools.title + title: publicRoutes.resources.subRoutes?.personalFinanceTools.title }, ...personalFinanceTools.map(({ alias, key, name }) => { return { @@ -23,8 +23,8 @@ export const routes: Routes = [ return GfProductPageComponent; } ), - path: `${publicRoutes.resources.subRoutes.personalFinanceTools.subRoutes.product.path}-${alias ?? key}`, - title: `${publicRoutes.resources.subRoutes.personalFinanceTools.subRoutes.product.title} ${name}` + path: `${publicRoutes.resources.subRoutes?.personalFinanceTools.subRoutes?.product.path}-${alias ?? key}`, + title: `${publicRoutes.resources.subRoutes?.personalFinanceTools.subRoutes?.product.title} ${name}` }; }) ]; diff --git a/apps/client/src/app/pages/resources/resources-page.routes.ts b/apps/client/src/app/pages/resources/resources-page.routes.ts index 107988238..6e59f0995 100644 --- a/apps/client/src/app/pages/resources/resources-page.routes.ts +++ b/apps/client/src/app/pages/resources/resources-page.routes.ts @@ -16,22 +16,22 @@ export const routes: Routes = [ import('./overview/resources-overview.routes').then((m) => m.routes) }, { - path: publicRoutes.resources.subRoutes.glossary.path, + path: publicRoutes.resources.subRoutes?.glossary.path, loadChildren: () => import('./glossary/resources-glossary.routes').then((m) => m.routes) }, { - path: publicRoutes.resources.subRoutes.guides.path, + path: publicRoutes.resources.subRoutes?.guides.path, loadChildren: () => import('./guides/resources-guides.routes').then((m) => m.routes) }, { - path: publicRoutes.resources.subRoutes.markets.path, + path: publicRoutes.resources.subRoutes?.markets.path, loadChildren: () => import('./markets/resources-markets.routes').then((m) => m.routes) }, { - path: publicRoutes.resources.subRoutes.personalFinanceTools.path, + path: publicRoutes.resources.subRoutes?.personalFinanceTools.path, loadChildren: () => import('./personal-finance-tools/personal-finance-tools-page.routes').then( (m) => m.routes diff --git a/apps/client/src/app/pages/user-account/user-account-page.component.ts b/apps/client/src/app/pages/user-account/user-account-page.component.ts index 7341660f2..24c150408 100644 --- a/apps/client/src/app/pages/user-account/user-account-page.component.ts +++ b/apps/client/src/app/pages/user-account/user-account-page.component.ts @@ -6,16 +6,16 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { diamondOutline, keyOutline, settingsOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -25,20 +25,19 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./user-account-page.scss'], templateUrl: './user-account-page.html' }) -export class GfUserAccountPageComponent implements OnDestroy, OnInit { +export class GfUserAccountPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -73,9 +72,4 @@ export class GfUserAccountPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/user-account/user-account-page.routes.ts b/apps/client/src/app/pages/user-account/user-account-page.routes.ts index 5d0f5b202..7eac1298e 100644 --- a/apps/client/src/app/pages/user-account/user-account-page.routes.ts +++ b/apps/client/src/app/pages/user-account/user-account-page.routes.ts @@ -18,14 +18,14 @@ export const routes: Routes = [ title: internalRoutes.account.title }, { - path: internalRoutes.account.subRoutes.membership.path, + path: internalRoutes.account.subRoutes?.membership.path, component: GfUserAccountMembershipComponent, - title: internalRoutes.account.subRoutes.membership.title + title: internalRoutes.account.subRoutes?.membership.title }, { - path: internalRoutes.account.subRoutes.access.path, + path: internalRoutes.account.subRoutes?.access.path, component: GfUserAccountAccessComponent, - title: internalRoutes.account.subRoutes.access.title + title: internalRoutes.account.subRoutes?.access.title } ], component: GfUserAccountPageComponent, diff --git a/apps/client/src/app/pages/zen/zen-page.component.ts b/apps/client/src/app/pages/zen/zen-page.component.ts index 280f33c25..633f15885 100644 --- a/apps/client/src/app/pages/zen/zen-page.component.ts +++ b/apps/client/src/app/pages/zen/zen-page.component.ts @@ -2,15 +2,19 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { TabConfiguration, User } from '@ghostfolio/common/interfaces'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { albumsOutline, analyticsOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -19,20 +23,19 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./zen-page.scss'], templateUrl: './zen-page.html' }) -export class GfZenPageComponent implements OnDestroy, OnInit { +export class GfZenPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.tabs = [ @@ -59,9 +62,4 @@ export class GfZenPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/zen/zen-page.routes.ts b/apps/client/src/app/pages/zen/zen-page.routes.ts index 60e163ca4..38929dc85 100644 --- a/apps/client/src/app/pages/zen/zen-page.routes.ts +++ b/apps/client/src/app/pages/zen/zen-page.routes.ts @@ -16,9 +16,9 @@ export const routes: Routes = [ component: GfHomeOverviewComponent }, { - path: internalRoutes.zen.subRoutes.holdings.path, + path: internalRoutes.zen.subRoutes?.holdings.path, component: GfHomeHoldingsComponent, - title: internalRoutes.home.subRoutes.holdings.title + title: internalRoutes.home.subRoutes?.holdings.title } ], component: GfZenPageComponent, diff --git a/apps/client/src/app/services/token-storage.service.ts b/apps/client/src/app/services/token-storage.service.ts index 5b9a29a08..f54aab828 100644 --- a/apps/client/src/app/services/token-storage.service.ts +++ b/apps/client/src/app/services/token-storage.service.ts @@ -1,19 +1,11 @@ -import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; - import { Injectable } from '@angular/core'; import { KEY_TOKEN } from './settings-storage.service'; -import { UserService } from './user/user.service'; @Injectable({ providedIn: 'root' }) export class TokenStorageService { - public constructor( - private userService: UserService, - private webAuthnService: WebAuthnService - ) {} - public getToken(): string { return ( window.sessionStorage.getItem(KEY_TOKEN) || @@ -25,23 +17,7 @@ export class TokenStorageService { if (staySignedIn) { window.localStorage.setItem(KEY_TOKEN, token); } - window.sessionStorage.setItem(KEY_TOKEN, token); - } - - public signOut() { - const utmSource = window.localStorage.getItem('utm_source'); - if (this.webAuthnService.isEnabled()) { - this.webAuthnService.deregister().subscribe(); - } - - window.localStorage.clear(); - window.sessionStorage.clear(); - - this.userService.remove(); - - if (utmSource) { - window.localStorage.setItem('utm_source', utmSource); - } + window.sessionStorage.setItem(KEY_TOKEN, token); } } diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index bd9d7d04c..44b63e056 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -1,3 +1,4 @@ +import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { Filter, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -26,7 +27,8 @@ export class UserService extends ObservableStore { public constructor( private deviceService: DeviceDetectorService, private dialog: MatDialog, - private http: HttpClient + private http: HttpClient, + private webAuthnService: WebAuthnService ) { super({ trackStateHistory: true }); @@ -93,10 +95,40 @@ export class UserService extends ObservableStore { return this.getFilters().length > 0; } - public remove() { + public reset() { this.setState({ user: null }, UserStoreActions.RemoveUser); } + public signOut() { + const utmSource = window.localStorage.getItem('utm_source'); + + if (this.webAuthnService.isEnabled()) { + this.webAuthnService.deregister().subscribe(); + } + + window.localStorage.clear(); + window.sessionStorage.clear(); + + void this.clearAllCookies(); + + this.reset(); + + if (utmSource) { + window.localStorage.setItem('utm_source', utmSource); + } + } + + private async clearAllCookies() { + if (!('cookieStore' in window)) { + console.warn('Cookie Store API not available in this browser'); + return; + } + + const cookies = await cookieStore.getAll(); + + await Promise.all(cookies.map(({ name }) => cookieStore.delete(name))); + } + private fetchUser(): Observable { return this.http.get('/api/v1/user').pipe( map((user) => { diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index d878a63bf..cf0ede34d 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -427,7 +427,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -439,7 +439,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -499,7 +499,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -587,11 +587,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -619,11 +619,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -691,7 +691,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -759,7 +759,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -1011,7 +1011,7 @@ El preu de mercat actual és apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -1079,7 +1079,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1099,7 +1099,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1111,7 +1111,7 @@ Mapatge de Símbols apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -1127,7 +1127,7 @@ Configuració del Proveïdor de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -1135,7 +1135,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -1143,11 +1143,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1163,7 +1163,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1171,7 +1171,7 @@ Notes apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1319,7 +1319,7 @@ Recollida de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1391,7 +1391,15 @@ Està segur que vol eliminar aquesta plataforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1415,7 +1423,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1431,7 +1439,7 @@ Plataformes apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1439,7 +1447,7 @@ Etiquetes apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1463,7 +1471,7 @@ Està segur que vol eliminar aquesta etiqueta? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -1555,11 +1563,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1607,7 +1615,7 @@ Punt de Referència apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1943,7 +1951,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2351,7 +2359,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2363,7 +2371,7 @@ 1 any apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2375,7 +2383,7 @@ 5 anys apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2395,7 +2403,7 @@ Màx apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2567,7 +2575,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -2623,7 +2631,7 @@ Localització apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3223,11 +3231,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3271,7 +3279,7 @@ General apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -3279,7 +3287,7 @@ Núvol apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -3291,7 +3299,7 @@ Autoallotjament apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -3492,7 +3500,7 @@ Mercats apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -4852,11 +4860,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -5433,7 +5441,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5453,7 +5461,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -5473,7 +5481,7 @@ any apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5493,7 +5501,7 @@ anys apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -5505,7 +5513,7 @@ Perfils d’actius apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -5645,7 +5653,7 @@ Dipòsit libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -5661,7 +5669,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -5673,7 +5681,7 @@ Estalvi libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -5745,7 +5753,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5777,7 +5785,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6293,7 +6301,7 @@ Vàlid fins a apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -6673,7 +6681,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6725,7 +6733,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6777,7 +6785,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7081,7 +7089,7 @@ Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7187,7 +7195,7 @@ of apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7195,7 +7203,7 @@ daily requests apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7203,7 +7211,7 @@ Remove API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7211,7 +7219,7 @@ Do you really want to delete the API key? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7303,7 +7311,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7379,7 +7387,7 @@ Please enter your Ghostfolio API key. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7411,7 +7419,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7708,7 +7716,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 54c3f7de8..cad4fcc92 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -106,7 +106,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -118,7 +118,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -234,11 +234,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -266,11 +266,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -366,7 +366,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -382,7 +382,7 @@ Anlageprofile apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -398,7 +398,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -986,7 +986,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1006,7 +1006,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1018,7 +1018,7 @@ Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1082,7 +1082,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1094,7 +1094,7 @@ 1J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1106,7 +1106,7 @@ 5J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1126,7 +1126,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -1298,7 +1298,7 @@ Lokalität apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1438,7 +1438,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1714,7 +1714,7 @@ Märkte apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1934,7 +1934,7 @@ Aktuelle Woche apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2018,7 +2018,7 @@ Kommentar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2546,7 +2546,7 @@ Einlage libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -2562,7 +2562,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -2574,7 +2574,7 @@ Ersparnisse libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -2666,11 +2666,11 @@ Das Formular konnte nicht validiert werden apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2686,7 +2686,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2802,7 +2802,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3126,7 +3126,7 @@ Symbol Zuordnung apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3190,7 +3190,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3318,7 +3318,7 @@ Gültig bis apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3638,11 +3638,11 @@ Das Anlageprofil konnte nicht gespeichert werden apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3845,6 +3845,14 @@ 306 + + Explore + Entdecke + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By Bis @@ -3866,7 +3874,7 @@ Aktuelles Jahr apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -3882,11 +3890,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3902,7 +3910,7 @@ Das Anlageprofil wurde gespeichert apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3910,7 +3918,7 @@ Möchtest du diese Plattform wirklich löschen? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 @@ -3918,7 +3926,7 @@ Plattformen apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -4262,7 +4270,7 @@ Scraper Konfiguration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4802,11 +4810,11 @@ Die Scraper Konfiguration konnte nicht geparsed werden apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5500,7 +5508,7 @@ Möchtest du diesen Tag wirklich löschen? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5792,7 +5800,7 @@ Der aktuelle Marktpreis ist apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5800,7 +5808,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5960,7 +5968,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5980,7 +5988,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6028,7 +6036,7 @@ Jahr apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6048,7 +6056,7 @@ Jahre apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6068,7 +6076,7 @@ Finanzmarktdaten synchronisieren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6080,7 +6088,7 @@ Allgemein apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6088,7 +6096,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6100,7 +6108,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6293,7 +6301,7 @@ Berücksichtigen in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6697,7 +6705,7 @@ Fehler apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6749,7 +6757,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6801,7 +6809,7 @@ Schliessen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7105,7 +7113,7 @@ API-Schlüssel setzen apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7211,7 +7219,7 @@ von apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7219,7 +7227,7 @@ täglichen Anfragen apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7227,7 +7235,7 @@ API-Schlüssel löschen apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7235,7 +7243,7 @@ Möchtest du den API-Schlüssel wirklich löschen? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7327,7 +7335,7 @@ Speichern apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7403,7 +7411,7 @@ Bitte gebe deinen Ghostfolio API-Schlüssel ein. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7435,7 +7443,7 @@ Verzögert apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7443,7 +7451,7 @@ Sofort apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7451,7 +7459,7 @@ Standardmarktpreis apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7459,7 +7467,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7467,7 +7475,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7475,7 +7483,7 @@ HTTP Request-Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7483,7 +7491,7 @@ Tagesende apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7491,7 +7499,7 @@ in Echtzeit apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7732,7 +7740,7 @@ () wird bereits verwendet. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7740,7 +7748,7 @@ Bei der Änderung zu () ist ein Fehler aufgetreten. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Aktueller Monat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ neu apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index ba5a38d67..a904cbae8 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -107,7 +107,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -119,7 +119,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -235,11 +235,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -267,11 +267,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -367,7 +367,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -383,7 +383,7 @@ Perfiles de activos. apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -399,7 +399,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -971,7 +971,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -991,7 +991,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1003,7 +1003,7 @@ Etiquetas apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1067,7 +1067,7 @@ Desde principio de año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1079,7 +1079,7 @@ 1 año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1091,7 +1091,7 @@ 5 años apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1111,7 +1111,7 @@ Máximo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -1283,7 +1283,7 @@ Ubicación apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1423,7 +1423,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1699,7 +1699,7 @@ Mercados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1919,7 +1919,7 @@ Semana actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2003,7 +2003,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2515,7 +2515,7 @@ Ahorros libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -2531,7 +2531,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -2551,7 +2551,7 @@ Depósito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -2651,7 +2651,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2663,11 +2663,11 @@ No se pudo validar el formulario apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2787,7 +2787,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3111,7 +3111,7 @@ Mapeo de símbolos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3175,7 +3175,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3303,7 +3303,7 @@ Válido hasta apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3623,11 +3623,11 @@ No se pudo guardar el perfil del activo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3822,6 +3822,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By Por @@ -3843,7 +3851,7 @@ Año actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -3859,11 +3867,11 @@ ¿La URL? apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3879,7 +3887,7 @@ El perfil del activo ha sido guardado apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3887,7 +3895,7 @@ ¿Realmente deseas eliminar esta plataforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 @@ -3895,7 +3903,7 @@ Plataformas apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -4239,7 +4247,7 @@ Configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4779,11 +4787,11 @@ No se pudo analizar la configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5477,7 +5485,7 @@ ¿Realmente deseas eliminar esta etiqueta? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5769,7 +5777,7 @@ El precio actual de mercado es apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5777,7 +5785,7 @@ Prueba apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5937,7 +5945,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5957,7 +5965,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6005,7 +6013,7 @@ año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6025,7 +6033,7 @@ años apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6045,7 +6053,7 @@ Recopilación de datos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6057,7 +6065,7 @@ General apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6065,7 +6073,7 @@ Nube apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6077,7 +6085,7 @@ Autoalojamiento apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6270,7 +6278,7 @@ Incluir en apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6674,7 +6682,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6726,7 +6734,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6778,7 +6786,7 @@ Cerrar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7082,7 +7090,7 @@ Configurar clave API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7188,7 +7196,7 @@ de apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7196,7 +7204,7 @@ solicitudes diarias apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7204,7 +7212,7 @@ Eliminar clave API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7212,7 +7220,7 @@ ¿Realmente deseas eliminar la clave API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7304,7 +7312,7 @@ Ahorrar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7380,7 +7388,7 @@ Por favor, ingresa tu clave API de Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7412,7 +7420,7 @@ Perezoso apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7420,7 +7428,7 @@ Instantáneo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7428,7 +7436,7 @@ Precio de mercado por defecto apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7436,7 +7444,7 @@ Modo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7444,7 +7452,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7452,7 +7460,7 @@ Encabezados de solicitud HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7460,7 +7468,7 @@ final del día apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7468,7 +7476,7 @@ en tiempo real apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7709,7 +7717,7 @@ () ya está en uso. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7717,7 +7725,7 @@ Ocurrió un error al actualizar a (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8068,7 +8076,7 @@ Mes actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8076,7 +8084,7 @@ nuevo apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index f343cb2ad..f01a3a505 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -114,7 +114,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -126,7 +126,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -186,7 +186,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -290,11 +290,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -322,11 +322,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -414,7 +414,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -438,7 +438,7 @@ Profils d’Actifs apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -454,7 +454,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -650,7 +650,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -670,7 +670,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -682,7 +682,7 @@ Équivalence de Symboles apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -690,7 +690,7 @@ Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -762,7 +762,7 @@ Étiquettes apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -898,11 +898,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -942,7 +942,7 @@ Référence apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1314,7 +1314,7 @@ CDA apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1326,7 +1326,7 @@ 1A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1338,7 +1338,7 @@ 5A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1358,7 +1358,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -1586,7 +1586,7 @@ Paramètres régionaux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1994,7 +1994,7 @@ Marchés apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -2098,7 +2098,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2438,7 +2438,7 @@ Dépôt libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -2858,7 +2858,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -2870,7 +2870,7 @@ Épargne libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -2934,7 +2934,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2966,7 +2966,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3302,7 +3302,7 @@ Valide jusqu’au apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3622,11 +3622,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3821,6 +3821,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By By @@ -3842,7 +3850,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -3858,11 +3866,11 @@ Lien apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3878,7 +3886,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3886,7 +3894,7 @@ Voulez-vous vraiment supprimer cette plateforme ? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 @@ -3894,7 +3902,7 @@ Platformes apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -4238,7 +4246,7 @@ Configuration du Scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4778,11 +4786,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5476,7 +5484,7 @@ Confirmez la suppression de ce tag ? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5768,7 +5776,7 @@ Le prix actuel du marché est apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5776,7 +5784,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5936,7 +5944,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5956,7 +5964,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6004,7 +6012,7 @@ année apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6024,7 +6032,7 @@ années apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6044,7 +6052,7 @@ Collecter les données apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6056,7 +6064,7 @@ Général apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6064,7 +6072,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6076,7 +6084,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6269,7 +6277,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6673,7 +6681,7 @@ Erreur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6725,7 +6733,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6777,7 +6785,7 @@ Fermer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7081,7 +7089,7 @@ Définir clé API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7187,7 +7195,7 @@ sur apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7195,7 +7203,7 @@ requêtes journalières apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7203,7 +7211,7 @@ Retirer la clé API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7211,7 +7219,7 @@ Voulez-vous vraiment supprimer la clé API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7303,7 +7311,7 @@ Sauvegarder apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7379,7 +7387,7 @@ Veuillez saisir votre clé API Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7411,7 +7419,7 @@ Paresseux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Instantané apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Prix du marché par défaut apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Selecteur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ En-têtes de requête HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ fin de journée apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ temps réel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7708,7 +7716,7 @@ () est déjà utilisé. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ Une erreur s’est produite lors de la mise à jour vers (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 20bfd1b33..5f06af0b4 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -107,7 +107,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -119,7 +119,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -235,11 +235,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -267,11 +267,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -367,7 +367,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -383,7 +383,7 @@ Profilo dell’asset apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -399,7 +399,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -971,7 +971,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -991,7 +991,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1003,7 +1003,7 @@ Tag apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1067,7 +1067,7 @@ anno corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1079,7 +1079,7 @@ 1 anno apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1091,7 +1091,7 @@ 5 anni apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1111,7 +1111,7 @@ Massimo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -1283,7 +1283,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1423,7 +1423,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1699,7 +1699,7 @@ Mercati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1919,7 +1919,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2003,7 +2003,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2515,7 +2515,7 @@ Risparmio libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -2531,7 +2531,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -2551,7 +2551,7 @@ Deposito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -2651,7 +2651,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2663,11 +2663,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2787,7 +2787,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3111,7 +3111,7 @@ Mappatura dei simboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3175,7 +3175,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3303,7 +3303,7 @@ Valido fino a apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3623,11 +3623,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3822,6 +3822,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By By @@ -3843,7 +3851,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -3859,11 +3867,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3879,7 +3887,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3887,7 +3895,7 @@ Vuoi davvero eliminare questa piattaforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 @@ -3895,7 +3903,7 @@ Piattaforme apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -4239,7 +4247,7 @@ Configurazione dello scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4779,11 +4787,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5477,7 +5485,7 @@ Sei sicuro di voler eliminare questo tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5769,7 +5777,7 @@ L’attuale prezzo di mercato è apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5777,7 +5785,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5937,7 +5945,7 @@ Settimana corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5957,7 +5965,7 @@ Mese corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6005,7 +6013,7 @@ anno apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6025,7 +6033,7 @@ anni apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6045,7 +6053,7 @@ Raccolta Dati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6057,7 +6065,7 @@ Generale apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6065,7 +6073,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6077,7 +6085,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6270,7 +6278,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6674,7 +6682,7 @@ Errore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6726,7 +6734,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6778,7 +6786,7 @@ Chiudi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7082,7 +7090,7 @@ Imposta API Key apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7188,7 +7196,7 @@ di apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7196,7 +7204,7 @@ richieste giornaliere apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7204,7 +7212,7 @@ Rimuovi API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7212,7 +7220,7 @@ Vuoi davvero eliminare l’API key? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7304,7 +7312,7 @@ Salva apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7380,7 +7388,7 @@ Inserisci la tua API key di Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7412,7 +7420,7 @@ Pigro apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7420,7 +7428,7 @@ Istantaneo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7428,7 +7436,7 @@ Prezzo di mercato predefinito apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7436,7 +7444,7 @@ Modalità apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7444,7 +7452,7 @@ Selettore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7452,7 +7460,7 @@ Intestazioni della richiesta HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7460,7 +7468,7 @@ fine giornata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7468,7 +7476,7 @@ in tempo reale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7709,7 +7717,7 @@ () e gia in uso. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7717,7 +7725,7 @@ Si è verificato un errore durante l’aggiornamento di (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8068,7 +8076,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8076,7 +8084,7 @@ nuovo apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.ko.xlf b/apps/client/src/locales/messages.ko.xlf index 9b7c4192e..bb361b515 100644 --- a/apps/client/src/locales/messages.ko.xlf +++ b/apps/client/src/locales/messages.ko.xlf @@ -360,7 +360,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -372,7 +372,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -432,7 +432,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -520,11 +520,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -552,11 +552,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -604,7 +604,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -664,7 +664,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -960,7 +960,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -980,7 +980,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -992,7 +992,7 @@ 심볼 매핑 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -1008,7 +1008,7 @@ 스크래퍼 설정 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -1016,7 +1016,7 @@ 메모 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1224,11 +1224,11 @@ 링크 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1244,7 +1244,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1252,7 +1252,15 @@ 정말로 이 플랫폼을 삭제하시겠습니까? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1276,7 +1284,7 @@ 올해 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1292,7 +1300,7 @@ 플랫폼 apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1300,7 +1308,7 @@ 태그 apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1324,7 +1332,7 @@ 이 태그를 정말로 삭제하시겠습니까? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -1416,11 +1424,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1468,7 +1476,7 @@ 기준 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1672,7 +1680,7 @@ 이번주 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2140,7 +2148,7 @@ 연초 대비 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2152,7 +2160,7 @@ 1년 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2164,7 +2172,7 @@ 5년 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2184,7 +2192,7 @@ 맥스 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2340,7 +2348,7 @@ 장소 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2916,11 +2924,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3160,7 +3168,7 @@ 시장 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -4160,7 +4168,7 @@ 보증금 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -4456,11 +4464,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -5005,7 +5013,7 @@ 자산 프로필 apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -5121,7 +5129,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -5133,7 +5141,7 @@ 저금 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -5205,7 +5213,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5237,7 +5245,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5737,7 +5745,7 @@ 유효기간 apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5801,7 +5809,7 @@ 현재 시장가격은 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5809,7 +5817,7 @@ 시험 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5977,7 +5985,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -5989,7 +5997,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -6029,7 +6037,7 @@ 년도 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6049,7 +6057,7 @@ 연령 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6082,7 +6090,7 @@ 셀프 호스팅 apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6094,7 +6102,7 @@ 데이터 수집 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6106,7 +6114,7 @@ 일반적인 apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6114,7 +6122,7 @@ 구름 apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6294,7 +6302,7 @@ 포함 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6698,7 +6706,7 @@ 오류 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6710,7 +6718,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6794,7 +6802,7 @@ 닫다 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7106,7 +7114,7 @@ API 키 설정 apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7220,7 +7228,7 @@ ~의 apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7228,7 +7236,7 @@ API 키를 정말로 삭제하시겠습니까? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7236,7 +7244,7 @@ API 키 제거 apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7244,7 +7252,7 @@ 일일 요청 apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7328,7 +7336,7 @@ 구하다 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7404,7 +7412,7 @@ Ghostfolio API 키를 입력하세요. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7436,7 +7444,7 @@ 방법 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7444,7 +7452,7 @@ 기본 시장 가격 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7452,7 +7460,7 @@ 선택자 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7460,7 +7468,7 @@ 즉각적인 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7468,7 +7476,7 @@ 게으른 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7476,7 +7484,7 @@ HTTP 요청 헤더 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7484,7 +7492,7 @@ 실시간 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7492,7 +7500,7 @@ 하루의 끝 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7733,7 +7741,7 @@ ()은(는) 이미 사용 중입니다. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7741,7 +7749,7 @@ ()로 업데이트하는 동안 오류가 발생했습니다. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8068,7 +8076,7 @@ 이번 달 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8076,7 +8084,7 @@ 새로운 apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 158ff4fb1..fbd87961c 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -106,7 +106,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -118,7 +118,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -234,11 +234,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -266,11 +266,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -366,7 +366,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -382,7 +382,7 @@ Activa Profiel apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -398,7 +398,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -970,7 +970,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -990,7 +990,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1002,7 +1002,7 @@ Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1066,7 +1066,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1078,7 +1078,7 @@ 1J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1090,7 +1090,7 @@ 5J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1110,7 +1110,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -1282,7 +1282,7 @@ Locatie apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1422,7 +1422,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1698,7 +1698,7 @@ Markten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1918,7 +1918,7 @@ Huidige week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2002,7 +2002,7 @@ Opmerking apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2514,7 +2514,7 @@ Besparingen libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -2530,7 +2530,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -2550,7 +2550,7 @@ Storting libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -2650,7 +2650,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2662,11 +2662,11 @@ Het formulier kon niet worden gevalideerd. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2786,7 +2786,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3110,7 +3110,7 @@ Symbool toewijzen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3174,7 +3174,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3302,7 +3302,7 @@ Geldig tot apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3622,11 +3622,11 @@ Kon het assetprofiel niet opslaan apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3821,9 +3821,17 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By - Door + Tegen apps/client/src/app/pages/portfolio/fire/fire-page.html 139 @@ -3842,7 +3850,7 @@ Huidig jaar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -3858,11 +3866,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3878,7 +3886,7 @@ Het activaprofiel is opgeslagen. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3886,7 +3894,7 @@ Wil je dit platform echt verwijderen? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 @@ -3894,7 +3902,7 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -4238,7 +4246,7 @@ Scraper instellingen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4778,11 +4786,11 @@ De scraperconfiguratie kon niet worden geparseerd apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5476,7 +5484,7 @@ Weet u zetker dat u dit label wilt verwijderen? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5768,7 +5776,7 @@ De huidige markt waarde is apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5776,7 +5784,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5821,7 +5829,7 @@ Argentina - Argentinië + Argentinië libs/ui/src/lib/i18n.ts 78 @@ -5936,7 +5944,7 @@ Week tot nu toe apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5956,7 +5964,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6004,7 +6012,7 @@ jaar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6024,7 +6032,7 @@ jaren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6044,7 +6052,7 @@ Data Verzamelen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6056,7 +6064,7 @@ Algemeen apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6064,7 +6072,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6076,7 +6084,7 @@ Zelf Hosten apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6269,7 +6277,7 @@ Opnemen in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6673,7 +6681,7 @@ Fout apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6702,7 +6710,7 @@ , based on your total assets of - opnemen, dit is gebaseerd op uw totale vermogen van + opnemen, dit is gebaseerd op uw totale vermogen van apps/client/src/app/pages/portfolio/fire/fire-page.html 96 @@ -6725,7 +6733,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6777,7 +6785,7 @@ Sluiten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7054,7 +7062,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 een gebruiksvriendelijke applicatie voor vermogensbeheer waarmee particulieren hun aandelen, ETF's of cryptovaluta kunnen volgen en weloverwogen, datagestuurde beleggingsbeslissingen kunnen nemen. + Ghostfolio is een gebruiksvriendelijke applicatie voor vermogensbeheer waarmee particulieren hun aandelen, ETF's of cryptovaluta kunnen volgen en weloverwogen, datagestuurde beleggingsbeslissingen kunnen nemen. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7081,7 +7089,7 @@ API-sleutel instellen apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7187,7 +7195,7 @@ van apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7195,7 +7203,7 @@ dagelijkse verzoeken apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7203,7 +7211,7 @@ Verwijder API-sleutel apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7211,7 +7219,7 @@ Wilt u de API-sleutel echt verwijderen? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7303,7 +7311,7 @@ Opslaan apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7379,7 +7387,7 @@ Voer uw Ghostfolio API-sleutel in. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7411,7 +7419,7 @@ Lui apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Direct apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Standaard Marktprijs apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Kiezer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ HTTP Verzoek Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ eind van de dag apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7708,7 +7716,7 @@ () is al in gebruik. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ Er is een fout opgetreden tijdens het updaten naar (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Huidige maand apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ nieuw apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index bc8b948cd..c7c07967f 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -240,7 +240,7 @@ please - please + proszę apps/client/src/app/pages/pricing/pricing-page.html 333 @@ -284,7 +284,7 @@ with - with + z apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 87 @@ -351,7 +351,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -363,7 +363,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -423,7 +423,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -511,11 +511,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -543,11 +543,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -595,7 +595,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -655,12 +655,12 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 and is driven by the efforts of its contributors - and is driven by the efforts of its contributors + i jest rozwijany dzięki pracy jego współtwórców apps/client/src/app/pages/about/overview/about-overview-page.html 49 @@ -927,7 +927,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -947,7 +947,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -959,7 +959,7 @@ Mapowanie Symboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -975,7 +975,7 @@ Konfiguracja Scrapera apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -983,7 +983,7 @@ Notatka apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 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 - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1208,10 +1208,10 @@ Asset profile has been saved - Asset profile has been saved + Profil zasobu został zapisany apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1219,12 +1219,20 @@ Czy naprawdę chcesz usunąć tę platformę? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + Eksploruj + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 By - By + Przez apps/client/src/app/pages/portfolio/fire/fire-page.html 139 @@ -1240,10 +1248,10 @@ Current year - Current year + Obecny rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1259,7 +1267,7 @@ Platformy apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1267,7 +1275,7 @@ Tagi apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1291,7 +1299,7 @@ Czy naprawdę chcesz usunąć ten tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -1336,7 +1344,7 @@ No auto-renewal on membership. - No auto-renewal on membership. + Brak automatycznego odnawiania członkostwa. apps/client/src/app/components/user-account-membership/user-account-membership.html 74 @@ -1383,11 +1391,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1435,7 +1443,7 @@ Poziom Odniesienia (Benchmark) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1572,7 +1580,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 + Kod źródłowy jest w pełni dostępny jako oprogramowanie open source (OSS) na licencji AGPL-3.0 license apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -1636,10 +1644,10 @@ Current week - Current week + Obecny tydzień apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2107,7 +2115,7 @@ Liczony od początku roku (year-to-date) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2119,7 +2127,7 @@ 1 rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2131,7 +2139,7 @@ 5 lat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2140,7 +2148,7 @@ Performance with currency effect - Performance with currency effect + Wynik z efektem walutowym apps/client/src/app/pages/portfolio/analysis/analysis-page.html 135 @@ -2151,7 +2159,7 @@ Maksimum apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2307,7 +2315,7 @@ Ustawienia Regionalne apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2368,7 +2376,7 @@ this is projected to increase to - this is projected to increase to + prognozuje się wzrost tej kwoty do apps/client/src/app/pages/portfolio/fire/fire-page.html 147 @@ -2580,7 +2588,7 @@ for - for + dla apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 128 @@ -2880,14 +2888,14 @@ Could not parse scraper configuration - Could not parse scraper configuration + Nie udało się przetworzyć konfiguracji scrapera apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3036,7 +3044,7 @@ per week - per week + na tydzień apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 130 @@ -3127,7 +3135,7 @@ Rynki apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3212,7 +3220,7 @@ Edit access - Edit access + Edytuj dostęp apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 11 @@ -3316,7 +3324,7 @@ Get access to 80’000+ tickers from over 50 exchanges - Get access to 80’000+ tickers from over 50 exchanges + Uzyskaj dostęp do 80 000+ tickerów z ponad 50 giełd apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 84 @@ -3452,7 +3460,7 @@ less than - less than + mniej niż apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -3516,7 +3524,7 @@ Ghostfolio Status - Ghostfolio Status + Ghostfolio Status apps/client/src/app/pages/about/overview/about-overview-page.html 62 @@ -3524,7 +3532,7 @@ with your university e-mail address - with your university e-mail address + używając swojego adresu e-mail uczelni apps/client/src/app/pages/pricing/pricing-page.html 348 @@ -3556,7 +3564,7 @@ and a safe withdrawal rate (SWR) of - and a safe withdrawal rate (SWR) of + oraz bezpiecznej stopy wypłaty (SWR) na poziomie apps/client/src/app/pages/portfolio/fire/fire-page.html 108 @@ -3796,7 +3804,7 @@ or start a discussion at - or start a discussion at + lub rozpocznij dyskusję n apps/client/src/app/pages/about/overview/about-overview-page.html 94 @@ -4008,7 +4016,7 @@ Latest activities - Latest activities + Ostatnie transakcje apps/client/src/app/pages/public/public-page.html 210 @@ -4076,7 +4084,7 @@ Looking for a student discount? - Looking for a student discount? + Szukasz zniżki studenckiej? apps/client/src/app/pages/pricing/pricing-page.html 342 @@ -4116,7 +4124,7 @@ annual interest rate - annual interest rate + rocznej stopy zwrotu apps/client/src/app/pages/portfolio/fire/fire-page.html 185 @@ -4127,7 +4135,7 @@ Depozyt libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -4423,11 +4431,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4456,7 +4464,7 @@ Sustainable retirement income - Sustainable retirement income + Zrównoważony dochód na emeryturze apps/client/src/app/pages/portfolio/fire/fire-page.html 41 @@ -4589,7 +4597,7 @@ per month - per month + miesięcznie apps/client/src/app/pages/portfolio/fire/fire-page.html 94 @@ -4609,7 +4617,7 @@ Website of Thomas Kaul - Website of Thomas Kaul + Strona internetowa Thomas'a Kaul'a apps/client/src/app/pages/about/overview/about-overview-page.html 44 @@ -4841,7 +4849,7 @@ Request it - Request it + Porpoś o apps/client/src/app/pages/pricing/pricing-page.html 344 @@ -4952,7 +4960,7 @@ Profile aktywów apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -4961,7 +4969,7 @@ , - , + , apps/client/src/app/pages/portfolio/fire/fire-page.html 145 @@ -4985,7 +4993,7 @@ contact us - contact us + skontaktuj się z nami apps/client/src/app/pages/pricing/pricing-page.html 336 @@ -5041,7 +5049,7 @@ Interest - Udział + Odsetki apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html 69 @@ -5052,7 +5060,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -5064,7 +5072,7 @@ Oszczędności libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -5136,7 +5144,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5168,7 +5176,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5309,7 +5317,7 @@ No Activities - No Activities + Brak transakcji apps/client/src/app/components/admin-market-data/admin-market-data.component.ts 146 @@ -5389,7 +5397,7 @@ View Details - View Details + Zobacz szczegóły apps/client/src/app/components/admin-users/admin-users.html 225 @@ -5409,7 +5417,7 @@ Sign in with OpenID Connect - Sign in with OpenID Connect + Zaloguj się za pomocą OpenID Connect apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 55 @@ -5417,7 +5425,7 @@ Buy - Zakup + Kupno apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 31 @@ -5461,7 +5469,7 @@ Sell - Sprzedaj + Sprzedaż apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 44 @@ -5521,7 +5529,7 @@ Authentication - Authentication + Uwierzytelnianie apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 54 @@ -5617,7 +5625,7 @@ If you retire today, you would be able to withdraw - If you retire today, you would be able to withdraw + Gdybyś przeszedł na emeryturę dziś, mógłbyś wypłacać apps/client/src/app/pages/portfolio/fire/fire-page.html 68 @@ -5668,7 +5676,7 @@ Ważność do apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5768,7 +5776,7 @@ Obecna cena rynkowa wynosi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5776,7 +5784,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5821,7 +5829,7 @@ Argentina - Argentina + Argentyna libs/ui/src/lib/i18n.ts 78 @@ -5877,7 +5885,7 @@ here - here + tutaj apps/client/src/app/pages/pricing/pricing-page.html 347 @@ -5885,7 +5893,7 @@ Close Holding - Close Holding + Zamknij pozycję apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 441 @@ -5936,7 +5944,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5956,7 +5964,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6004,7 +6012,7 @@ rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6024,7 +6032,7 @@ lata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6044,7 +6052,7 @@ Gromadzenie Danych apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6056,7 +6064,7 @@ Informacje Ogólne apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6064,7 +6072,7 @@ Rozwiązanie w Chmurze apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6076,7 +6084,7 @@ Własny Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6266,10 +6274,10 @@ Include in - Include in + Uwzględnij w apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6550,7 +6558,7 @@ View Holding - View Holding + Podgląd inwestycji libs/ui/src/lib/activities-table/activities-table.component.html 450 @@ -6673,7 +6681,7 @@ Błąd apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6694,7 +6702,7 @@ Oops! Could not update access. - Oops! Could not update access. + Ups! Nie udało się zaktualizować dostępu. apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 178 @@ -6702,7 +6710,7 @@ , based on your total assets of - , based on your total assets of + , na podstawie całkowitej wartości aktywów wynoszącej apps/client/src/app/pages/portfolio/fire/fire-page.html 96 @@ -6725,7 +6733,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6777,7 +6785,7 @@ Zamknij apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6822,7 +6830,7 @@ Role - Role + Rola apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 33 @@ -6862,7 +6870,7 @@ If you plan to open an account at - If you plan to open an account at + Jeśli planujesz otworzyć konto w apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -6894,7 +6902,7 @@ send an e-mail to - send an e-mail to + wyślij e-mail do apps/client/src/app/pages/about/overview/about-overview-page.html 87 @@ -6966,7 +6974,7 @@ , assuming a - , assuming a + , przyjmując apps/client/src/app/pages/portfolio/fire/fire-page.html 174 @@ -6974,7 +6982,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 + aby skorzystać z naszego linku polecającego i otrzymać roczną subskrypcję Ghostfolio Premium apps/client/src/app/pages/pricing/pricing-page.html 340 @@ -6982,7 +6990,7 @@ can be self-hosted - może być hostowany samodzielnie + może być hostowana samodzielnie apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 178 @@ -7006,7 +7014,7 @@ can be used anonymously - może być używany anonimowo + może być używana anonimowo apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 217 @@ -7018,7 +7026,7 @@ cannot be used anonymously - nie może być używany anonimowo + nie może być używana anonimowo apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 224 @@ -7054,7 +7062,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 to aplikacja do zarządzania majątkiem, przeznaczona dla osób prywatnych do śledzenia akcji, ETF‑ów i kryptowalut oraz podejmowania solidnych, opartych na danych decyzji inwestycyjnych. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7081,7 +7089,7 @@ Skonfiguruj klucz API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7187,7 +7195,7 @@ z apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7195,7 +7203,7 @@ codzienne żądania apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7203,7 +7211,7 @@ Usuń klucz API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7211,7 +7219,7 @@ Czy na pewno chcesz usunąć klucz API?? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7303,7 +7311,7 @@ Zapisz apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7368,7 +7376,7 @@ Check the system status at - Check the system status at + Sprawdź status systemu n apps/client/src/app/pages/about/overview/about-overview-page.html 57 @@ -7379,12 +7387,12 @@ Wprowadź swój klucz API Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 Change with currency effect - Change with currency effect + Zmiana z efektem walutowym apps/client/src/app/pages/portfolio/analysis/analysis-page.html 116 @@ -7411,7 +7419,7 @@ Leniwy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Natychmiastowy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Domyślna cena rynkowa apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Tryb apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ Nagłówki żądań HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ koniec dnia apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ w czasie rzeczywistym apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7524,7 +7532,7 @@ The project has been initiated by - The project has been initiated by + Projekt został zainicjowany przez apps/client/src/app/pages/about/overview/about-overview-page.html 40 @@ -7548,7 +7556,7 @@ Total amount - Total amount + Wartość portfela apps/client/src/app/pages/portfolio/analysis/analysis-page.html 95 @@ -7640,7 +7648,7 @@ Find account, holding or page... - Find account, holding or page... + Znajdź konto, pozycję lub stronę... libs/ui/src/lib/assistant/assistant.component.ts 115 @@ -7708,7 +7716,7 @@ () jest już w użyciu. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ Wystąpił błąd podczas aktualizacji do (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7890,7 +7898,7 @@ Fee Ratio - Fee Ratio + Wskaźnik opłat apps/client/src/app/pages/i18n/i18n-page.html 152 @@ -7898,7 +7906,7 @@ The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) - The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Opłaty przekraczają ${thresholdMax}% twojej całkowitej wartości inwestycji (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 @@ -7906,7 +7914,7 @@ The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) - The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Opłaty nie przekraczają ${thresholdMax}% twojej całkowitej wartości inwestycji (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8064,10 +8072,10 @@ Current month - Current month + Bieżący miesiąc apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ nowy apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts @@ -8325,7 +8333,7 @@ Account Cluster Risks - Account Cluster Risks + Ryzyka skupienia w obrębie rachunków apps/client/src/app/pages/i18n/i18n-page.html 14 @@ -8333,7 +8341,7 @@ Asset Class Cluster Risks - Asset Class Cluster Risks + Ryzyka skupienia w obrębie klas aktywów apps/client/src/app/pages/i18n/i18n-page.html 39 @@ -8341,7 +8349,7 @@ Currency Cluster Risks - Currency Cluster Risks + Ryzyka koncentracji walutowej apps/client/src/app/pages/i18n/i18n-page.html 83 @@ -8349,7 +8357,7 @@ Economic Market Cluster Risks - Economic Market Cluster Risks + Ryzyka skupienia w obrębie segmentów rynku gospodarczego apps/client/src/app/pages/i18n/i18n-page.html 106 @@ -8357,7 +8365,7 @@ Emergency Fund - Emergency Fund + Fundusz Awaryjny apps/client/src/app/pages/i18n/i18n-page.html 144 @@ -8365,7 +8373,7 @@ Fees - Fees + Opłaty apps/client/src/app/pages/i18n/i18n-page.html 161 @@ -8373,7 +8381,7 @@ Liquidity - Liquidity + Płynność apps/client/src/app/pages/i18n/i18n-page.html 70 @@ -8381,7 +8389,7 @@ Buying Power - Buying Power + Siła nabywcza apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8389,7 +8397,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Your buying power is below ${thresholdMin} ${baseCurrency} + Twoja siła nabywcza jest niższa niż ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8397,7 +8405,7 @@ Your buying power is 0 ${baseCurrency} - Your buying power is 0 ${baseCurrency} + Twoja siła nabywcza to 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8405,7 +8413,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Your buying power exceeds ${thresholdMin} ${baseCurrency} + Twoja siła nabywcza przekracza ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8413,7 +8421,7 @@ Regional Market Cluster Risks - Regional Market Cluster Risks + Ryzyka skupienia w obrębie regionów rynkowych apps/client/src/app/pages/i18n/i18n-page.html 163 @@ -8421,7 +8429,7 @@ No results found... - No results found... + Nie znaleziono wyników... libs/ui/src/lib/assistant/assistant.html 51 @@ -8429,7 +8437,7 @@ Developed Markets - Developed Markets + Rynki rozwinięte apps/client/src/app/pages/i18n/i18n-page.html 109 @@ -8437,7 +8445,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% + Udział rynków rozwiniętych w Twojej obecnej inwestycji (${developedMarketsValueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 112 @@ -8445,7 +8453,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% + Udział rynków rozwiniętych w Twojej obecnej inwestycji (${developedMarketsValueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 117 @@ -8453,7 +8461,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynków rozwiniętych w Twojej obecnej inwestycji (${developedMarketsValueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 122 @@ -8461,7 +8469,7 @@ Emerging Markets - Emerging Markets + Rynki wschodzące apps/client/src/app/pages/i18n/i18n-page.html 127 @@ -8469,7 +8477,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% + Udział rynków wschodzących w obecnej inwestycji (${emergingMarketsValueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 130 @@ -8477,7 +8485,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% + Udział rynków wschodzących w obecnej inwestycji (${emergingMarketsValueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 135 @@ -8485,7 +8493,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynków wschodzących w obecnej inwestycji (${emergingMarketsValueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 140 @@ -8493,7 +8501,7 @@ No accounts have been set up - No accounts have been set up + Nie utworzono żadnych kont apps/client/src/app/pages/i18n/i18n-page.html 21 @@ -8501,7 +8509,7 @@ Your net worth is managed by 0 accounts - Your net worth is managed by 0 accounts + Twój majątek jest zarządzany przez 0 kont apps/client/src/app/pages/i18n/i18n-page.html 33 @@ -8509,7 +8517,7 @@ Asia-Pacific - Asia-Pacific + Asia-Pacific apps/client/src/app/pages/i18n/i18n-page.html 165 @@ -8517,7 +8525,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku Azji i Pacyfiku w Twojej obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 167 @@ -8525,7 +8533,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku Azji i Pacyfiku w Twojej obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 171 @@ -8533,7 +8541,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku Azji i Pacyfiku w Twojej obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 175 @@ -8541,7 +8549,7 @@ Emerging Markets - Emerging Markets + Rynki wschodzące apps/client/src/app/pages/i18n/i18n-page.html 180 @@ -8549,7 +8557,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynków wschodzących w Twojej obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 183 @@ -8557,7 +8565,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynków wschodzących w Twojej obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 187 @@ -8565,7 +8573,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynków wschodzących w Twojej obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 191 @@ -8573,7 +8581,7 @@ Europe - Europe + Europa apps/client/src/app/pages/i18n/i18n-page.html 195 @@ -8581,7 +8589,7 @@ The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku europejskiego w obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 197 @@ -8589,7 +8597,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku europejskiego w obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 201 @@ -8597,7 +8605,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku europejskiego w obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 205 @@ -8605,7 +8613,7 @@ Japan - Japan + Japonia apps/client/src/app/pages/i18n/i18n-page.html 209 @@ -8613,7 +8621,7 @@ The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku japońskiego w obecnej inwestycj (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 211 @@ -8621,7 +8629,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku japońskiego w obecnej inwestycj (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 215 @@ -8629,7 +8637,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku japońskiego w obecnej inwestycj (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 219 @@ -8637,7 +8645,7 @@ North America - North America + Północna Ameryka apps/client/src/app/pages/i18n/i18n-page.html 223 @@ -8645,7 +8653,7 @@ The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku Ameryki Północnej w obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 225 @@ -8653,7 +8661,7 @@ The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku Ameryki Północnej w obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 229 @@ -8661,7 +8669,7 @@ The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku Ameryki Północnej w obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 233 @@ -8669,7 +8677,7 @@ Find Ghostfolio on GitHub - Find Ghostfolio on GitHub + Znajdź Ghostfolio na GitHub apps/client/src/app/pages/about/overview/about-overview-page.html 99 @@ -8681,7 +8689,7 @@ Join the Ghostfolio Slack community - Join the Ghostfolio Slack community + Dołącz do społeczności Ghostfolio na Slacku apps/client/src/app/pages/about/overview/about-overview-page.html 109 @@ -8689,7 +8697,7 @@ Follow Ghostfolio on X (formerly Twitter) - Follow Ghostfolio on X (formerly Twitter) + Śledź Ghostfolio na X (poprzednio Twitter) apps/client/src/app/pages/about/overview/about-overview-page.html 118 @@ -8697,7 +8705,7 @@ Send an e-mail - Send an e-mail + Wyślij e-mail apps/client/src/app/pages/about/overview/about-overview-page.html 89 @@ -8709,7 +8717,7 @@ Registration Date - Registration Date + Data rejestracji apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html 45 @@ -8717,7 +8725,7 @@ Follow Ghostfolio on LinkedIn - Follow Ghostfolio on LinkedIn + Śledź Ghostfolio na LinkedIn apps/client/src/app/pages/about/overview/about-overview-page.html 147 @@ -8725,7 +8733,7 @@ Ghostfolio is an independent & bootstrapped business - Ghostfolio is an independent & bootstrapped business + Ghostfolio to niezależna & samofinansująca się firma apps/client/src/app/pages/about/overview/about-overview-page.html 157 @@ -8733,7 +8741,7 @@ Support Ghostfolio - Support Ghostfolio + Wesprzyj Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 166 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index e49f13b96..72c1e6a3e 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -114,7 +114,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -126,7 +126,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -186,7 +186,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -290,11 +290,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -322,11 +322,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -414,7 +414,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -438,7 +438,7 @@ Perfil de Ativos apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -454,7 +454,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -766,11 +766,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -810,7 +810,7 @@ Referência apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -942,7 +942,7 @@ Depósito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -1214,7 +1214,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1234,7 +1234,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1246,7 +1246,7 @@ Marcadores apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1310,7 +1310,7 @@ AATD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1322,7 +1322,7 @@ 1A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1334,7 +1334,7 @@ 5A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -1354,7 +1354,7 @@ Máx apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -1590,7 +1590,7 @@ Localidade apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1974,7 +1974,7 @@ Mercados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -2078,7 +2078,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2142,7 +2142,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2758,7 +2758,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -2770,7 +2770,7 @@ Poupanças libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -2806,7 +2806,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3118,7 +3118,7 @@ Mapeamento de Símbolo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3238,7 +3238,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3302,7 +3302,7 @@ Válido até apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3622,11 +3622,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3821,6 +3821,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By By @@ -3842,7 +3850,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -3858,11 +3866,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3878,7 +3886,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3886,7 +3894,7 @@ Deseja mesmo eliminar esta plataforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 @@ -3894,7 +3902,7 @@ Plataformas apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -4238,7 +4246,7 @@ Configuração do raspador apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4778,11 +4786,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5476,7 +5484,7 @@ Você realmente deseja excluir esta tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5768,7 +5776,7 @@ O preço de mercado atual é apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5776,7 +5784,7 @@ Teste apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5936,7 +5944,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5956,7 +5964,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6004,7 +6012,7 @@ ano apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6024,7 +6032,7 @@ anos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6044,7 +6052,7 @@ Coleta de dados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6056,7 +6064,7 @@ geral apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6064,7 +6072,7 @@ Nuvem apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6076,7 +6084,7 @@ Auto-hospedagem apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6269,7 +6277,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6673,7 +6681,7 @@ Erro apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6725,7 +6733,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6777,7 +6785,7 @@ Fechar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7081,7 +7089,7 @@ Definir chave de API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7187,7 +7195,7 @@ de apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7195,7 +7203,7 @@ solicitações diárias apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7203,7 +7211,7 @@ Remover chave de API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7211,7 +7219,7 @@ Você realmente deseja excluir a chave de API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7303,7 +7311,7 @@ Guardar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7379,7 +7387,7 @@ Please enter your Ghostfolio API key. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7411,7 +7419,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Preço de mercado padrão apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7708,7 +7716,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index abe33aa80..7f51580d7 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -311,7 +311,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -323,7 +323,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -383,7 +383,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -471,11 +471,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -503,11 +503,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -595,7 +595,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -619,7 +619,7 @@ Varlık Profili apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -635,7 +635,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -859,7 +859,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -879,7 +879,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -891,7 +891,7 @@ Sembol Eşleştirme apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -907,7 +907,7 @@ Veri Toplayıcı Yapılandırması apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -915,7 +915,7 @@ Not apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1015,7 +1015,7 @@ Etiketler apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1107,11 +1107,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1127,7 +1127,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1135,7 +1135,15 @@ Bu platformu silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1159,7 +1167,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1175,7 +1183,7 @@ Platformlar apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1251,11 +1259,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1303,7 +1311,7 @@ Karşılaştırma Ölçütü apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1507,7 +1515,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -1975,7 +1983,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1987,7 +1995,7 @@ 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1999,7 +2007,7 @@ 5Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2019,7 +2027,7 @@ Maks. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2447,11 +2455,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2703,7 +2711,7 @@ Piyasalar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3611,7 +3619,7 @@ Para Yatırma libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -3907,11 +3915,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4392,7 +4400,7 @@ Geçerli tarih apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -4472,7 +4480,7 @@ Yerel Ayarlar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -4748,7 +4756,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -4760,7 +4768,7 @@ Tasarruflar libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -4832,7 +4840,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4864,7 +4872,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5476,7 +5484,7 @@ Bu etiketi silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -5768,7 +5776,7 @@ Şu anki piyasa fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5776,7 +5784,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5936,7 +5944,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5956,7 +5964,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6004,7 +6012,7 @@ Yıl apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6024,7 +6032,7 @@ Yıllar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6044,7 +6052,7 @@ Veri Toplama apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6056,7 +6064,7 @@ Genel apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6064,7 +6072,7 @@ Bulut apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6076,7 +6084,7 @@ Kendini Barındırma apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6269,7 +6277,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6673,7 +6681,7 @@ Hata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6725,7 +6733,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6777,7 +6785,7 @@ Kapat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7081,7 +7089,7 @@ API anahtarını ayarla apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7187,7 +7195,7 @@ ın apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7195,7 +7203,7 @@ günlük istekler apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7203,7 +7211,7 @@ API anahtarını kaldır apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7211,7 +7219,7 @@ API anahtarını silmek istediğinize emin misiniz? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7303,7 +7311,7 @@ Kaydet apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7379,7 +7387,7 @@ Lütfen Ghostfolio API anahtarınızı girin. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7411,7 +7419,7 @@ Tembel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Anında apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Varsayılan Piyasa Fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Mod apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Seçici apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ HTTP İstek Başlıkları apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ gün sonu apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ gerçek zamanlı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7708,7 +7716,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ Güncelleştirilirken bir hata oluştu (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ yeni apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 95ad50c7c..54bf5a08b 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -443,7 +443,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -455,7 +455,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -515,7 +515,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -603,11 +603,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -635,11 +635,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -707,7 +707,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -783,7 +783,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -999,7 +999,7 @@ Помилка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -1007,7 +1007,7 @@ Поточна ринкова ціна apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -1059,7 +1059,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 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 - 407 + 410 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 - 384 + 387 @@ -1107,7 +1107,7 @@ Конфігурація скребка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -1115,7 +1115,7 @@ Тест apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -1123,11 +1123,11 @@ URL apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1143,7 +1143,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1151,7 +1151,7 @@ Примітка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1307,7 +1307,7 @@ Збір даних apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1379,7 +1379,15 @@ Ви дійсно хочете видалити цю платформу? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1403,7 +1411,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1427,7 +1435,7 @@ Дійсне до apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -1439,7 +1447,7 @@ з apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -1447,7 +1455,7 @@ щоденних запитів apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -1455,7 +1463,7 @@ Вилучити ключ API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -1463,7 +1471,7 @@ Встановити ключ API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -1471,7 +1479,7 @@ Платформи apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1479,7 +1487,7 @@ Теги apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1495,7 +1503,7 @@ Ви дійсно хочете видалити ключ API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -1503,7 +1511,7 @@ Будь ласка, введіть ваш ключ API Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -1587,7 +1595,7 @@ Ви дійсно хочете видалити цей тег? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -1667,11 +1675,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1719,7 +1727,7 @@ Порівняльний показник apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2079,7 +2087,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2283,7 +2291,7 @@ Зберегти apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2563,7 +2571,7 @@ З початку року apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2575,7 +2583,7 @@ 1 рік apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2587,7 +2595,7 @@ 5 років apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2607,7 +2615,7 @@ Максимум apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2855,7 +2863,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -2911,7 +2919,7 @@ Локалізація apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3503,11 +3511,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3551,7 +3559,7 @@ Загальні apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -3559,7 +3567,7 @@ Хмара apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -3571,7 +3579,7 @@ Самохостинг apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -3772,7 +3780,7 @@ Ринки apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -5208,11 +5216,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -6163,7 +6171,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -6183,7 +6191,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -6203,7 +6211,7 @@ рік apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6223,7 +6231,7 @@ роки apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6235,7 +6243,7 @@ Профілі активів apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -6375,7 +6383,7 @@ Депозит libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -6391,7 +6399,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -6403,7 +6411,7 @@ Заощадження libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -6491,7 +6499,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6523,7 +6531,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6555,7 +6563,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6615,7 +6623,7 @@ Закрити apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7411,7 +7419,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7419,7 +7427,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7427,7 +7435,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7435,7 +7443,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7443,7 +7451,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7451,7 +7459,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7459,7 +7467,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7467,7 +7475,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7708,7 +7716,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7716,7 +7724,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8067,7 +8075,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8075,7 +8083,7 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index b6a54edfe..7db54e0c4 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -334,7 +334,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -346,7 +346,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -404,7 +404,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -490,11 +490,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -521,11 +521,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -570,7 +570,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -625,7 +625,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -893,7 +893,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -912,7 +912,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -923,7 +923,7 @@ Symbol Mapping apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -937,14 +937,14 @@ Scraper Configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1129,11 +1129,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1148,14 +1148,21 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 Do you really want to delete this platform? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1176,7 +1183,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1190,14 +1197,14 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1219,7 +1226,7 @@ Do you really want to delete this tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -1301,11 +1308,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1349,7 +1356,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1534,7 +1541,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -1965,7 +1972,7 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -1976,7 +1983,7 @@ 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -1987,7 +1994,7 @@ 5Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2005,7 +2012,7 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2144,7 +2151,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2680,11 +2687,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2903,7 +2910,7 @@ Markets apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3808,7 +3815,7 @@ Deposit libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -4074,11 +4081,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4570,7 +4577,7 @@ Asset Profiles apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -4673,7 +4680,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -4684,7 +4691,7 @@ Savings libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -4751,7 +4758,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4782,7 +4789,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5222,7 +5229,7 @@ Valid until apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5281,14 +5288,14 @@ The current market price is apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5438,7 +5445,7 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -5449,7 +5456,7 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -5485,7 +5492,7 @@ year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5504,7 +5511,7 @@ years apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -5534,7 +5541,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -5545,7 +5552,7 @@ Data Gathering apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -5556,14 +5563,14 @@ General apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -5721,7 +5728,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6082,7 +6089,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6093,7 +6100,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6172,7 +6179,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6454,7 +6461,7 @@ Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -6557,28 +6564,28 @@ of apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 Do you really want to delete the API key? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 Remove API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 daily requests apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -6652,7 +6659,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6724,7 +6731,7 @@ Please enter your Ghostfolio API key. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -6752,56 +6759,56 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7016,14 +7023,14 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7315,14 +7322,14 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index ac81eebc7..be465e9df 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -360,7 +360,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -372,7 +372,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -432,7 +432,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -520,11 +520,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -552,11 +552,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -604,7 +604,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -664,7 +664,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -936,7 +936,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -956,7 +956,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -968,7 +968,7 @@ 代码映射 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -984,7 +984,7 @@ 刮削配置 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -992,7 +992,7 @@ 笔记 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 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 - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1220,7 +1220,7 @@ 资产概况已保存 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1228,7 +1228,15 @@ 您真的要删除这个平台吗? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 112 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1252,7 +1260,7 @@ 当前年份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 @@ -1268,7 +1276,7 @@ 平台 apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1276,7 +1284,7 @@ 标签 apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1300,7 +1308,7 @@ 您真的要删除此标签吗? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 109 @@ -1392,11 +1400,11 @@ 无法验证表单 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1444,7 +1452,7 @@ 基准 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1648,7 +1656,7 @@ 当前周 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 @@ -2116,7 +2124,7 @@ 年初至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 204 libs/ui/src/lib/assistant/assistant.component.ts @@ -2128,7 +2136,7 @@ 1年 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 libs/ui/src/lib/assistant/assistant.component.ts @@ -2140,7 +2148,7 @@ 5年 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -2160,7 +2168,7 @@ 最大限度 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 216 libs/ui/src/lib/assistant/assistant.component.ts @@ -2316,7 +2324,7 @@ 语言环境 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2892,11 +2900,11 @@ 无法解析抓取器配置 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3136,7 +3144,7 @@ 市场 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -4144,7 +4152,7 @@ 存款 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 385 + 390 @@ -4440,11 +4448,11 @@ 无法保存资产概况 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4989,7 +4997,7 @@ 资产概况 apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -5105,7 +5113,7 @@ libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 395 + 400 libs/ui/src/lib/i18n.ts @@ -5117,7 +5125,7 @@ 储蓄 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 405 + 410 @@ -5189,7 +5197,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5221,7 +5229,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5713,7 +5721,7 @@ 有效期至 apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5777,7 +5785,7 @@ 当前市场价格为 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5785,7 +5793,7 @@ 测试 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5953,7 +5961,7 @@ 本月至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 libs/ui/src/lib/assistant/assistant.component.ts @@ -5965,7 +5973,7 @@ 本周至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 196 libs/ui/src/lib/assistant/assistant.component.ts @@ -6005,7 +6013,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 208 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6025,7 +6033,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 212 libs/ui/src/lib/assistant/assistant.component.ts @@ -6058,7 +6066,7 @@ 自托管 apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6070,7 +6078,7 @@ 数据收集 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6082,7 +6090,7 @@ 一般的 apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6090,7 +6098,7 @@ apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6270,7 +6278,7 @@ 包含在 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6674,7 +6682,7 @@ 错误 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6726,7 +6734,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6778,7 +6786,7 @@ 关闭 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7082,7 +7090,7 @@ 设置 API 密钥 apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7188,7 +7196,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7196,7 +7204,7 @@ 每日请求 apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7204,7 +7212,7 @@ 移除 API 密钥 apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7212,7 +7220,7 @@ 您确定要删除此 API 密钥吗? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7304,7 +7312,7 @@ 保存 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7380,7 +7388,7 @@ 请输入您的 Ghostfolio API 密钥。 apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7412,7 +7420,7 @@ 延迟 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7420,7 +7428,7 @@ 即时 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7428,7 +7436,7 @@ 默认市场价格 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7436,7 +7444,7 @@ 模式 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7444,7 +7452,7 @@ 选择器 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7452,7 +7460,7 @@ HTTP 请求标头 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7460,7 +7468,7 @@ 收盘 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 230 @@ -7468,7 +7476,7 @@ 实时 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 234 @@ -7709,7 +7717,7 @@ () 已在使用中。 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7717,7 +7725,7 @@ 在更新到 () 时发生错误。 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -8068,7 +8076,7 @@ 当前月份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 200 @@ -8076,7 +8084,7 @@ 新增 apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss index b7a031bfa..142d2ea53 100644 --- a/apps/client/src/styles.scss +++ b/apps/client/src/styles.scss @@ -2,7 +2,7 @@ @import './styles/table'; @import './styles/variables'; -@import 'svgmap/dist/svgMap'; +@import 'svgmap/style.min'; :root { --dark-background: rgb(25, 25, 25); diff --git a/libs/common/src/lib/calculation-helper.ts b/libs/common/src/lib/calculation-helper.ts index 76b38f9b2..2097fa52a 100644 --- a/libs/common/src/lib/calculation-helper.ts +++ b/libs/common/src/lib/calculation-helper.ts @@ -36,14 +36,16 @@ export function getAnnualizedPerformancePercent({ return new Big(0); } -export function getIntervalFromDateRange( - aDateRange: DateRange, - portfolioStart = new Date(0) -) { - let endDate = endOfDay(new Date()); - let startDate = portfolioStart; +export function getIntervalFromDateRange(params: { + dateRange: DateRange; + endDate?: Date; + startDate?: Date; +}) { + const { dateRange } = params; + let endDate = params.endDate ?? endOfDay(new Date()); + let startDate = params.startDate ?? new Date(0); - switch (aDateRange) { + switch (dateRange) { case '1d': startDate = max([startDate, subDays(resetHours(new Date()), 1)]); break; @@ -75,8 +77,8 @@ export function getIntervalFromDateRange( break; default: // '2024', '2023', '2022', etc. - endDate = endOfYear(new Date(aDateRange)); - startDate = max([startDate, new Date(aDateRange)]); + endDate = endOfYear(new Date(dateRange)); + startDate = max([startDate, new Date(dateRange)]); } return { endDate, startDate }; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 5da0e0122..08fa2f030 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -51,6 +51,14 @@ export const ASSET_CLASS_MAPPING = new Map([ [AssetClass.REAL_ESTATE, []] ]); +export const BULL_BOARD_COOKIE_NAME = 'bull_board_token'; + +/** + * WARNING: This route is mirrored in `apps/client/proxy.conf.json`. + * If you update this value, you must also update the proxy configuration. + */ +export const BULL_BOARD_ROUTE = '/admin/queues'; + export const CACHE_TTL_NO_CACHE = 1; export const CACHE_TTL_INFINITE = 0; diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index 25779cf39..a339c6dab 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -25,7 +25,7 @@ describe('Helper', () => { it('Get decimal number with group (dot notation)', () => { expect( - extractNumberFromString({ locale: 'de-CH', value: '99’999.99' }) + extractNumberFromString({ locale: 'de-CH', value: `99'999.99` }) ).toEqual(99999.99); }); @@ -54,12 +54,12 @@ describe('Helper', () => { }); it('Get de-CH number format group', () => { - expect(getNumberFormatGroup('de-CH')).toEqual('’'); + expect(getNumberFormatGroup('de-CH')).toEqual(`'`); }); it('Get de-CH number format group when it is default', () => { languageGetter.mockReturnValue('de-CH'); - expect(getNumberFormatGroup()).toEqual('’'); + expect(getNumberFormatGroup()).toEqual(`'`); }); it('Get de-DE number format group', () => { diff --git a/libs/common/src/lib/interfaces/lookup-item.interface.ts b/libs/common/src/lib/interfaces/lookup-item.interface.ts index fa91ed690..6cedeca09 100644 --- a/libs/common/src/lib/interfaces/lookup-item.interface.ts +++ b/libs/common/src/lib/interfaces/lookup-item.interface.ts @@ -7,7 +7,7 @@ export interface LookupItem { assetSubClass: AssetSubClass; currency: string; dataProviderInfo: DataProviderInfo; - dataSource: DataSource; + dataSource: DataSource | null; name: string; symbol: string; } diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index 620cc00e9..c4ef2e3dc 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -3,19 +3,52 @@ import { Market, MarketAdvanced } from '@ghostfolio/common/types'; import { AssetClass, AssetSubClass, DataSource, Tag } from '@prisma/client'; import { Country } from './country.interface'; +import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; import { Holding } from './holding.interface'; import { Sector } from './sector.interface'; export interface PortfolioPosition { activitiesCount: number; allocationInPercentage: number; + + /** @deprecated */ assetClass?: AssetClass; + + /** @deprecated */ assetClassLabel?: string; + + assetProfile: Pick< + EnhancedSymbolProfile, + | 'assetClass' + | 'assetSubClass' + | 'countries' + | 'currency' + | 'dataSource' + | 'holdings' + | 'name' + | 'sectors' + | 'symbol' + | 'url' + > & { + assetClassLabel?: string; + assetSubClassLabel?: string; + }; + + /** @deprecated */ assetSubClass?: AssetSubClass; + + /** @deprecated */ assetSubClassLabel?: string; + + /** @deprecated */ countries: Country[]; + + /** @deprecated */ currency: string; + + /** @deprecated */ dataSource: DataSource; + dateOfFirstActivity: Date; dividend: number; exchange?: string; @@ -23,24 +56,38 @@ export interface PortfolioPosition { grossPerformancePercent: number; grossPerformancePercentWithCurrencyEffect: number; grossPerformanceWithCurrencyEffect: number; + + /** @deprecated */ holdings: Holding[]; + investment: number; marketChange?: number; marketChangePercent?: number; marketPrice: number; markets?: { [key in Market]: number }; marketsAdvanced?: { [key in MarketAdvanced]: number }; + + /** @deprecated */ name: string; + netPerformance: number; netPerformancePercent: number; netPerformancePercentWithCurrencyEffect: number; netPerformanceWithCurrencyEffect: number; quantity: number; + + /** @deprecated */ sectors: Sector[]; + + /** @deprecated */ symbol: string; + tags?: Tag[]; type?: string; + + /** @deprecated */ url?: string; + valueInBaseCurrency?: number; valueInPercentage?: number; } diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 79fbf8707..8db6b39bb 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -6,10 +6,6 @@ export interface PortfolioSummary extends PortfolioPerformance { annualizedPerformancePercent: number; annualizedPerformancePercentWithCurrencyEffect: number; cash: number; - - /** @deprecated use totalInvestmentValueWithCurrencyEffect instead */ - committedFunds: number; - dateOfFirstActivity: Date; dividendInBaseCurrency: number; emergencyFund: { diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts index 4a087ad16..eae14cec6 100644 --- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts @@ -14,16 +14,31 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { [symbol: string]: Pick< PortfolioPosition, | 'allocationInPercentage' + + /** @deprecated */ | 'assetClass' + | 'assetProfile' + + /** @deprecated */ | 'countries' | 'currency' + + /** @deprecated */ | 'dataSource' | 'dateOfFirstActivity' | 'markets' + + /** @deprecated */ | 'name' | 'netPerformancePercentWithCurrencyEffect' + + /** @deprecated */ | 'sectors' + + /** @deprecated */ | 'symbol' + + /** @deprecated */ | 'url' | 'valueInBaseCurrency' | 'valueInPercentage' diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index cb4eb175b..f9cb19562 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -4,15 +4,16 @@ import { Role } from '@prisma/client'; export const permissions = { accessAdminControl: 'accessAdminControl', + accessAdminControlBullBoard: 'accessAdminControlBullBoard', accessAssistant: 'accessAssistant', accessHoldingsChart: 'accessHoldingsChart', createAccess: 'createAccess', createAccount: 'createAccount', createAccountBalance: 'createAccountBalance', + createActivity: 'createActivity', createApiKey: 'createApiKey', createMarketData: 'createMarketData', createMarketDataOfOwnAssetProfile: 'createMarketDataOfOwnAssetProfile', - createOrder: 'createOrder', createOwnTag: 'createOwnTag', createPlatform: 'createPlatform', createTag: 'createTag', @@ -21,8 +22,8 @@ export const permissions = { deleteAccess: 'deleteAccess', deleteAccount: 'deleteAccount', deleteAccountBalance: 'deleteAccountBalance', + deleteActivity: 'deleteActivity', deleteAuthDevice: 'deleteAuthDevice', - deleteOrder: 'deleteOrder', deleteOwnUser: 'deleteOwnUser', deletePlatform: 'deletePlatform', deleteTag: 'deleteTag', @@ -53,10 +54,10 @@ export const permissions = { toggleReadOnlyMode: 'toggleReadOnlyMode', updateAccount: 'updateAccount', updateAccess: 'updateAccess', + updateActivity: 'updateActivity', updateAuthDevice: 'updateAuthDevice', updateMarketData: 'updateMarketData', updateMarketDataOfOwnAssetProfile: 'updateMarketDataOfOwnAssetProfile', - updateOrder: 'updateOrder', updateOwnAccessToken: 'updateOwnAccessToken', updatePlatform: 'updatePlatform', updateTag: 'updateTag', @@ -74,19 +75,19 @@ export function getPermissions(aRole: Role): string[] { permissions.createAccess, permissions.createAccount, permissions.createAccountBalance, + permissions.createActivity, permissions.createWatchlistItem, permissions.deleteAccountBalance, permissions.deleteWatchlistItem, permissions.createMarketData, permissions.createMarketDataOfOwnAssetProfile, - permissions.createOrder, permissions.createOwnTag, permissions.createPlatform, permissions.createTag, permissions.deleteAccess, permissions.deleteAccount, + permissions.deleteActivity, permissions.deleteAuthDevice, - permissions.deleteOrder, permissions.deletePlatform, permissions.deleteTag, permissions.deleteUser, @@ -99,10 +100,10 @@ export function getPermissions(aRole: Role): string[] { permissions.readWatchlist, permissions.updateAccount, permissions.updateAccess, + permissions.updateActivity, permissions.updateAuthDevice, permissions.updateMarketData, permissions.updateMarketDataOfOwnAssetProfile, - permissions.updateOrder, permissions.updatePlatform, permissions.updateTag, permissions.updateUserSettings, @@ -125,15 +126,15 @@ export function getPermissions(aRole: Role): string[] { permissions.createAccess, permissions.createAccount, permissions.createAccountBalance, + permissions.createActivity, permissions.createMarketDataOfOwnAssetProfile, - permissions.createOrder, permissions.createOwnTag, permissions.createWatchlistItem, permissions.deleteAccess, permissions.deleteAccount, permissions.deleteAccountBalance, + permissions.deleteActivity, permissions.deleteAuthDevice, - permissions.deleteOrder, permissions.deleteWatchlistItem, permissions.readAiPrompt, permissions.readMarketDataOfOwnAssetProfile, @@ -141,9 +142,9 @@ export function getPermissions(aRole: Role): string[] { permissions.readWatchlist, permissions.updateAccount, permissions.updateAccess, + permissions.updateActivity, permissions.updateAuthDevice, permissions.updateMarketDataOfOwnAssetProfile, - permissions.updateOrder, permissions.updateUserSettings, permissions.updateViewMode ]; @@ -194,7 +195,7 @@ export function hasReadRestrictedAccessPermission({ return false; } - const access = user.accessesGet?.find(({ id }) => { + const access = user?.accessesGet?.find(({ id }) => { return id === impersonationId; }); diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 6d0a85fb2..063b4254c 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -33,6 +33,15 @@ export const personalFinanceTools: Product[] = [ origin: 'Switzerland', slogan: 'Simplicity for Complex Wealth' }, + { + founded: 2018, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'altruist', + name: 'Altruist', + origin: 'United States', + slogan: 'The wealth platform built for independent advisors' + }, { founded: 2023, hasFreePlan: false, @@ -116,6 +125,17 @@ export const personalFinanceTools: Product[] = [ origin: 'Switzerland', slogan: 'Schweizer Budget App für einfache & smarte Budgetplanung' }, + { + founded: 2015, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'boldin', + name: 'Boldin', + note: 'Originally named as NewRetirement', + origin: 'United States', + pricingPerYear: '$144', + slogan: 'Take control with retirement planning tools that begin with you' + }, { key: 'budgetpulse', name: 'BudgetPulse', @@ -841,6 +861,16 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$108', slogan: 'Build Financial Plans You Love.' }, + { + founded: 2022, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'prostocktracker', + name: 'Pro Stock Tracker', + origin: 'United Kingdom', + pricingPerYear: '$60', + slogan: 'The stock portfolio tracker built for long-term investors' + }, { founded: 2015, hasSelfHostingAbility: false, @@ -1014,6 +1044,16 @@ export const personalFinanceTools: Product[] = [ regions: ['Austria', 'Germany', 'Switzerland'], slogan: 'Dein Vermögen immer im Blick' }, + { + founded: 2025, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'turbobulls', + name: 'Turbobulls', + origin: 'Romania', + pricingPerYear: '€39.99', + slogan: 'Your complete financial dashboard. Actually private.' + }, { hasFreePlan: true, hasSelfHostingAbility: false, 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 25fad683d..6b58e6aec 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts @@ -7,6 +7,7 @@ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, + DestroyRef, ElementRef, Input, OnChanges, @@ -69,9 +70,9 @@ export class GfActivitiesFilterComponent implements OnChanges { protected selectedFilters: Filter[] = []; protected readonly separatorKeysCodes: number[] = [ENTER, COMMA]; - public constructor() { + public constructor(private destroyRef: DestroyRef) { this.searchControl.valueChanges - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((filterOrSearchTerm) => { if (filterOrSearchTerm) { const searchTerm = diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index 7e19833a9..79e2f31a1 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -197,7 +197,7 @@ mat-button type="button" [disabled]=" - !portfolioFilterForm.hasFilters() || portfolioFilterForm.disabled + !portfolioFilterForm.hasFilters() || portfolioFilterForm.disabled() " (click)="onResetFilters()" > @@ -210,7 +210,7 @@ type="button" [disabled]=" !portfolioFilterForm.filterForm.dirty || - portfolioFilterForm.disabled + portfolioFilterForm.disabled() " (click)="onApplyFilters()" > diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts index 291f4c973..b01403284 100644 --- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts +++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts @@ -3,7 +3,7 @@ import { ColorScheme } from '@ghostfolio/common/types'; import { DataSource } from '@prisma/client'; export interface BenchmarkDetailDialogParams { - colorScheme: ColorScheme; + colorScheme?: ColorScheme; dataSource: DataSource; deviceType: string; locale: string; diff --git a/libs/ui/src/lib/benchmark/benchmark.component.html b/libs/ui/src/lib/benchmark/benchmark.component.html index ab6db8887..8820f2ec1 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.html +++ b/libs/ui/src/lib/benchmark/benchmark.component.html @@ -15,7 +15,7 @@
{{ element?.name }}
- @if (showSymbol) { + @if (showSymbol()) {
{{ element?.symbol }}
@@ -98,7 +98,7 @@ @if (element?.performances?.allTimeHigh?.date) { } @@ -123,7 +123,7 @@
+ -} @else if (benchmarks?.length === 0) { +} @else if (benchmarks()?.length === 0) {
No data available
diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index adef1f41b..5dbc4648b 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -16,13 +16,15 @@ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - Output, - ViewChild + DestroyRef, + computed, + effect, + inject, + input, + output, + viewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -34,7 +36,6 @@ import { addIcons } from 'ionicons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, takeUntil } from 'rxjs'; import { translate } from '../i18n'; import { GfTrendIndicatorComponent } from '../trend-indicator/trend-indicator.component'; @@ -61,41 +62,58 @@ import { BenchmarkDetailDialogParams } from './benchmark-detail-dialog/interface styleUrls: ['./benchmark.component.scss'], templateUrl: './benchmark.component.html' }) -export class GfBenchmarkComponent implements OnChanges, OnDestroy { - @Input() benchmarks: Benchmark[]; - @Input() deviceType: string; - @Input() hasPermissionToDeleteItem: boolean; - @Input() locale = getLocale(); - @Input() showSymbol = true; - @Input() user: User; - - @Output() itemDeleted = new EventEmitter(); - - @ViewChild(MatSort) sort: MatSort; - - public dataSource = new MatTableDataSource([]); - public displayedColumns = [ - 'name', - 'date', - 'change', - 'marketCondition', - 'actions' - ]; - public isLoading = true; - public isNumber = isNumber; - public resolveMarketCondition = resolveMarketCondition; - public translate = translate; - - private unsubscribeSubject = new Subject(); - - public constructor( - private dialog: MatDialog, - private notificationService: NotificationService, - private route: ActivatedRoute, - private router: Router - ) { +export class GfBenchmarkComponent { + public readonly benchmarks = input.required(); + public readonly deviceType = input.required(); + public readonly hasPermissionToDeleteItem = input(); + public readonly locale = input(getLocale()); + public readonly showSymbol = input(true); + public readonly user = input(); + + public readonly itemDeleted = output(); + + protected readonly sort = viewChild(MatSort); + + protected readonly dataSource = new MatTableDataSource([]); + protected readonly displayedColumns = computed(() => { + return [ + 'name', + ...(this.user()?.settings?.isExperimentalFeatures + ? ['trend50d', 'trend200d'] + : []), + 'date', + 'change', + 'marketCondition', + 'actions' + ]; + }); + protected isLoading = true; + protected readonly isNumber = isNumber; + protected readonly resolveMarketCondition = resolveMarketCondition; + protected readonly translate = translate; + + private readonly destroyRef = inject(DestroyRef); + private readonly dialog = inject(MatDialog); + private readonly notificationService = inject(NotificationService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + + public constructor() { + effect(() => { + const benchmarks = this.benchmarks(); + + if (benchmarks) { + this.dataSource.data = benchmarks; + this.dataSource.sortingDataAccessor = getLowercase; + + this.dataSource.sort = this.sort() ?? null; + + this.isLoading = false; + } + }); + this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if ( params['benchmarkDetailDialog'] && @@ -112,30 +130,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { addIcons({ ellipsisHorizontal, trashOutline }); } - public ngOnChanges() { - if (this.benchmarks) { - this.dataSource.data = this.benchmarks; - this.dataSource.sortingDataAccessor = getLowercase; - - this.dataSource.sort = this.sort; - - this.isLoading = false; - } - - if (this.user?.settings?.isExperimentalFeatures) { - this.displayedColumns = [ - 'name', - 'trend50d', - 'trend200d', - 'date', - 'change', - 'marketCondition', - 'actions' - ]; - } - } - - public onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) { + protected onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) { this.notificationService.confirm({ confirmFn: () => { this.itemDeleted.emit({ dataSource, symbol }); @@ -145,17 +140,15 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { }); } - public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) { + protected onOpenBenchmarkDialog({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.router.navigate([], { queryParams: { dataSource, symbol, benchmarkDetailDialog: true } }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private openBenchmarkDetailDialog({ dataSource, symbol @@ -167,17 +160,17 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { data: { dataSource, symbol, - colorScheme: this.user?.settings?.colorScheme, - deviceType: this.deviceType, - locale: this.locale + colorScheme: this.user()?.settings?.colorScheme, + deviceType: this.deviceType(), + locale: this.locale() }, - height: this.deviceType === 'mobile' ? '98vh' : undefined, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + height: this.deviceType() === 'mobile' ? '98vh' : undefined, + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['.'], { relativeTo: this.route }); }); 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 8b472fc47..c8e281609 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -12,12 +12,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, ElementRef, Input, OnChanges, OnDestroy, - ViewChild, - output + output, + viewChild } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { @@ -55,7 +56,7 @@ import { startOfMonth, sub } from 'date-fns'; -import { isNil, isNumber } from 'lodash'; +import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { debounceTime } from 'rxjs'; @@ -90,8 +91,6 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { @Input() retirementDate: Date; @Input() savingsRate = 0; - @ViewChild('chartCanvas') chartCanvas: ElementRef; - public calculatorForm = this.formBuilder.group({ annualInterestRate: new FormControl(null), paymentPerPeriod: new FormControl(null), @@ -99,25 +98,33 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { projectedTotalAmount: new FormControl(null), retirementDate: new FormControl(null) }); + public chart: Chart<'bar'>; public isLoading = true; public minDate = addDays(new Date(), 1); public periodsToRetire = 0; protected readonly annualInterestRateChanged = output(); + protected readonly calculationCompleted = output(); + protected readonly projectedTotalAmountChanged = output(); protected readonly retirementDateChanged = output(); protected readonly savingsRateChanged = output(); private readonly CONTRIBUTION_PERIOD = 12; + private readonly DEFAULT_RETIREMENT_DATE = startOfMonth( addYears(new Date(), 10) ); + private readonly chartCanvas = + viewChild.required>('chartCanvas'); + public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private fireCalculatorService: FireCalculatorService, private formBuilder: FormBuilder ) { @@ -130,13 +137,13 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ); this.calculatorForm.valueChanges - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); this.calculatorForm.valueChanges - .pipe(debounceTime(500), takeUntilDestroyed()) + .pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe(() => { const { projectedTotalAmount, retirementDate } = this.calculatorForm.getRawValue(); @@ -151,7 +158,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.calculatorForm .get('annualInterestRate') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((annualInterestRate) => { if (annualInterestRate !== null) { this.annualInterestRateChanged.emit(annualInterestRate); @@ -159,7 +169,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); this.calculatorForm .get('paymentPerPeriod') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((savingsRate) => { if (savingsRate !== null) { this.savingsRateChanged.emit(savingsRate); @@ -167,7 +180,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); this.calculatorForm .get('projectedTotalAmount') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((projectedTotalAmount) => { if (projectedTotalAmount !== null) { this.projectedTotalAmountChanged.emit(projectedTotalAmount); @@ -175,7 +191,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }); this.calculatorForm .get('retirementDate') - ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((retirementDate) => { if (retirementDate !== null) { this.retirementDateChanged.emit(retirementDate); @@ -272,7 +291,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { const chartData = this.getChartData(); - if (this.chartCanvas) { + if (this.chartCanvas()) { if (this.chart) { this.chart.data.labels = chartData.labels; @@ -282,7 +301,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.chart.update(); } else { - this.chart = new Chart<'bar'>(this.chartCanvas.nativeElement, { + this.chart = new Chart<'bar'>(this.chartCanvas().nativeElement, { data: chartData, options: { plugins: { @@ -303,7 +322,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { }).format(totalAmount)}`; }, label: (context) => { - let label = context.dataset.label || ''; + let label = context.dataset.label ?? ''; if (label) { label += ': '; @@ -473,7 +492,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { 'projectedTotalAmount' )?.value; - if (!isNil(projectedTotalAmount)) { + if (projectedTotalAmount) { return projectedTotalAmount; } diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index 250eff578..3bb387ae4 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -193,7 +193,7 @@
+ Name @@ -102,7 +114,12 @@ - + Asset Profiles @@ -193,13 +210,13 @@

Platforms

- +

Tags

- +
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 446221058..d030abb82 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 @@ -22,20 +22,24 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, - OnInit + DestroyRef, + OnInit, + ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatMenuModule } from '@angular/material/menu'; import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; +import { get } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; +import { catchError, filter, of } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -52,6 +56,7 @@ import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; MatCardModule, MatMenuModule, MatProgressBarModule, + MatSortModule, MatTableModule, NgxSkeletonLoaderModule, RouterModule @@ -60,7 +65,9 @@ import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; styleUrls: ['./admin-settings.component.scss'], templateUrl: './admin-settings.component.html' }) -export class GfAdminSettingsComponent implements OnDestroy, OnInit { +export class GfAdminSettingsComponent implements OnInit { + @ViewChild(MatSort) sort: MatSort; + public dataSource = new MatTableDataSource(); public defaultDateFormat: string; public displayedColumns = [ @@ -74,14 +81,13 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { public isGhostfolioApiKeyValid: boolean; public isLoading = false; public pricingUrl: string; - - private unsubscribeSubject = new Subject(); - private user: User; + public user: User; public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private userService: UserService ) { @@ -90,7 +96,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -147,11 +153,6 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initialize() { this.isLoading = true; @@ -159,13 +160,15 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataProviders, settings }) => { const filteredProviders = dataProviders.filter(({ dataSource }) => { return dataSource !== 'MANUAL'; }); this.dataSource = new MatTableDataSource(filteredProviders); + this.dataSource.sort = this.sort; + this.dataSource.sortingDataAccessor = get; const ghostfolioApiKey = settings[ PROPERTY_API_KEY_GHOSTFOLIO @@ -185,7 +188,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { filter((status) => { return status !== null; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe((status) => { this.ghostfolioApiStatus = status; 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 98919abd8..463a817ff 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 @@ -45,7 +45,11 @@ Activities
- {{ element.activityCount }} + - @if (hasPermissionToDeleteItem) { + @if (hasPermissionToDeleteItem()) {
- + @if (isLoading()) { } -@if (dataSource.data.length > pageSize && !isLoading()) { +@if (dataSource.data.length > pageSize() && !isLoading()) {