diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b13c12d4..8f72750ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Extended the glossary of the resources page by _Stealth Wealth_ +- Extended the content of the pricing page - Added a _Storybook_ story for the holdings table component ### Changed @@ -18,11 +20,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the currency validation in the search functionality of the data provider service - Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service - Extracted the footer to a component +- Refactored the blog page component to standalone +- Improved the portfolio calculator unit tests to load the user currency from the exported file ### Fixed - Fixed an issue in the `csv` file import where custom asset profiles failed due to validation errors +- Fixed an issue with the total buy and sell calculation in the summary related to activities in a custom currency - Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service +- Fixed an issue where the scroll position was not restored when changing pages +- Fixed the word wrap in the menus of the activities table component +- Fixed the dark mode in the _As seen in_ section on the landing page ## 2.208.0 - 2025-10-11 diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts index 8850a6874..ccdbafac8 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator-test-utils.ts @@ -1,3 +1,5 @@ +import { Export } from '@ghostfolio/common/interfaces'; + import { readFileSync } from 'node:fs'; export const activityDummyData = { @@ -37,6 +39,6 @@ export const userDummyData = { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }; -export function loadActivityExportFile(filePath: string) { - return JSON.parse(readFileSync(filePath, 'utf8')).activities; +export function loadExportFile(filePath: string): Export { + return JSON.parse(readFileSync(filePath, 'utf8')); } diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts index 1f6f9dc2a..1ac0dcd16 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts @@ -1,8 +1,7 @@ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, - loadActivityExportFile, + loadExportFile, symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; @@ -16,9 +15,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Export } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; -import { Tag } from '@prisma/client'; import { Big } from 'big.js'; import { join } from 'node:path'; @@ -53,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let activityDtos: CreateOrderDto[]; + let exportResponse: Export; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; @@ -63,7 +62,7 @@ describe('PortfolioCalculator', () => { let redisCacheService: RedisCacheService; beforeAll(() => { - activityDtos = loadActivityExportFile( + exportResponse = loadExportFile( join(__dirname, '../../../../../../../test/import/ok/btceur.json') ); }); @@ -97,28 +96,27 @@ describe('PortfolioCalculator', () => { it.only('with BTCUSD buy (in EUR)', async () => { jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime()); - const activities: Activity[] = activityDtos.map((activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: 4.46, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: activity.dataSource, - name: 'Bitcoin', - symbol: activity.symbol - }, - tags: activity.tags?.map((id) => { - return { id } as Tag; - }), - unitPriceInAssetProfileCurrency: 44558.42 - })); + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: 4.46, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: activity.dataSource, + name: 'Bitcoin', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: 44558.42 + }) + ); const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ activities, calculationType: PerformanceCalculationType.ROAI, - currency: 'USD', + currency: exportResponse.user.settings.currency, userId: userDummyData.id }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts index a2d7e60d3..29413c6ad 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts @@ -1,8 +1,7 @@ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, - loadActivityExportFile, + loadExportFile, symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; @@ -16,9 +15,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Export } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; -import { Tag } from '@prisma/client'; import { Big } from 'big.js'; import { join } from 'node:path'; @@ -53,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let activityDtos: CreateOrderDto[]; + let exportResponse: Export; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; @@ -63,7 +62,7 @@ describe('PortfolioCalculator', () => { let redisCacheService: RedisCacheService; beforeAll(() => { - activityDtos = loadActivityExportFile( + exportResponse = loadExportFile( join(__dirname, '../../../../../../../test/import/ok/btcusd-short.json') ); }); @@ -97,28 +96,27 @@ describe('PortfolioCalculator', () => { it.only('with BTCUSD short sell (in USD)', async () => { jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime()); - const activities: Activity[] = activityDtos.map((activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: activity.dataSource, - name: 'Bitcoin', - symbol: activity.symbol - }, - tags: activity.tags?.map((id) => { - return { id } as Tag; - }), - unitPriceInAssetProfileCurrency: activity.unitPrice - })); + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: activity.dataSource, + name: 'Bitcoin', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: activity.unitPrice + }) + ); const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ activities, calculationType: PerformanceCalculationType.ROAI, - currency: 'USD', + currency: exportResponse.user.settings.currency, userId: userDummyData.id }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts index bdccb23e0..26b3325c2 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts @@ -1,8 +1,7 @@ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, - loadActivityExportFile, + loadExportFile, symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; @@ -16,9 +15,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Export } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; -import { Tag } from '@prisma/client'; import { Big } from 'big.js'; import { join } from 'node:path'; @@ -53,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let activityDtos: CreateOrderDto[]; + let exportResponse: Export; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; @@ -63,7 +62,7 @@ describe('PortfolioCalculator', () => { let redisCacheService: RedisCacheService; beforeAll(() => { - activityDtos = loadActivityExportFile( + exportResponse = loadExportFile( join(__dirname, '../../../../../../../test/import/ok/btcusd.json') ); }); @@ -97,28 +96,27 @@ describe('PortfolioCalculator', () => { it.only('with BTCUSD buy (in USD)', async () => { jest.useFakeTimers().setSystemTime(parseDate('2022-01-14').getTime()); - const activities: Activity[] = activityDtos.map((activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: 4.46, - SymbolProfile: { - ...symbolProfileDummyData, - currency: 'USD', - dataSource: activity.dataSource, - name: 'Bitcoin', - symbol: activity.symbol - }, - tags: activity.tags?.map((id) => { - return { id } as Tag; - }), - unitPriceInAssetProfileCurrency: 44558.42 - })); + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: 4.46, + SymbolProfile: { + ...symbolProfileDummyData, + currency: 'USD', + dataSource: activity.dataSource, + name: 'Bitcoin', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: 44558.42 + }) + ); const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ activities, calculationType: PerformanceCalculationType.ROAI, - currency: 'USD', + currency: exportResponse.user.settings.currency, userId: userDummyData.id }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index 4872a1004..0f1cdfff7 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -1,8 +1,7 @@ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, - loadActivityExportFile, + loadExportFile, symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; @@ -16,9 +15,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Export } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; -import { Tag } from '@prisma/client'; import { Big } from 'big.js'; import { join } from 'node:path'; @@ -53,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let activityDtos: CreateOrderDto[]; + let exportResponse: Export; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; @@ -63,7 +62,7 @@ describe('PortfolioCalculator', () => { let redisCacheService: RedisCacheService; beforeAll(() => { - activityDtos = loadActivityExportFile( + exportResponse = loadExportFile( join( __dirname, '../../../../../../../test/import/ok/novn-buy-and-sell-partially.json' @@ -100,28 +99,27 @@ describe('PortfolioCalculator', () => { it.only('with NOVN.SW buy and sell partially', async () => { jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime()); - const activities: Activity[] = activityDtos.map((activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, - currency: activity.currency, - dataSource: activity.dataSource, - name: 'Novartis AG', - symbol: activity.symbol - }, - tags: activity.tags?.map((id) => { - return { id } as Tag; - }), - unitPriceInAssetProfileCurrency: activity.unitPrice - })); + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + SymbolProfile: { + ...symbolProfileDummyData, + currency: activity.currency, + dataSource: activity.dataSource, + name: 'Novartis AG', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: activity.unitPrice + }) + ); const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ activities, calculationType: PerformanceCalculationType.ROAI, - currency: 'CHF', + currency: exportResponse.user.settings.currency, userId: userDummyData.id }); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index e6c71230b..e426a68fa 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -1,8 +1,7 @@ -import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { activityDummyData, - loadActivityExportFile, + loadExportFile, symbolProfileDummyData, userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; @@ -16,9 +15,9 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; import { parseDate } from '@ghostfolio/common/helper'; +import { Export } from '@ghostfolio/common/interfaces'; import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; -import { Tag } from '@prisma/client'; import { Big } from 'big.js'; import { join } from 'node:path'; @@ -53,7 +52,7 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { }); describe('PortfolioCalculator', () => { - let activityDtos: CreateOrderDto[]; + let exportResponse: Export; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; @@ -63,7 +62,7 @@ describe('PortfolioCalculator', () => { let redisCacheService: RedisCacheService; beforeAll(() => { - activityDtos = loadActivityExportFile( + exportResponse = loadExportFile( join( __dirname, '../../../../../../../test/import/ok/novn-buy-and-sell.json' @@ -100,28 +99,27 @@ describe('PortfolioCalculator', () => { it.only('with NOVN.SW buy and sell', async () => { jest.useFakeTimers().setSystemTime(parseDate('2022-04-11').getTime()); - const activities: Activity[] = activityDtos.map((activity) => ({ - ...activityDummyData, - ...activity, - date: parseDate(activity.date), - feeInAssetProfileCurrency: activity.fee, - SymbolProfile: { - ...symbolProfileDummyData, - currency: activity.currency, - dataSource: activity.dataSource, - name: 'Novartis AG', - symbol: activity.symbol - }, - tags: activity.tags?.map((id) => { - return { id } as Tag; - }), - unitPriceInAssetProfileCurrency: activity.unitPrice - })); + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + SymbolProfile: { + ...symbolProfileDummyData, + currency: activity.currency, + dataSource: activity.dataSource, + name: 'Novartis AG', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: activity.unitPrice + }) + ); const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ activities, calculationType: PerformanceCalculationType.ROAI, - currency: 'CHF', + currency: exportResponse.user.settings.currency, userId: userDummyData.id }); diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 19b0636c7..7d8ceecda 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -19,7 +19,7 @@ import { } from '@ghostfolio/common/config'; import { PortfolioDetails, - PortfolioDividends, + PortfolioDividendsResponse, PortfolioHoldingResponse, PortfolioHoldingsResponse, PortfolioInvestments, @@ -197,7 +197,7 @@ export class PortfolioController { 'filteredValueInBaseCurrency', 'grossPerformance', 'grossPerformanceWithCurrencyEffect', - 'interest', + 'interestInBaseCurrency', 'items', 'liabilities', 'netPerformance', @@ -305,7 +305,7 @@ export class PortfolioController { @Query('range') dateRange: DateRange = 'max', @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string - ): Promise { + ): Promise { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index a5bc10fbd..cb6eba5be 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -2105,7 +2105,7 @@ export class PortfolioService { ) .plus(fees) .toNumber(), - interest: interest.toNumber(), + interestInBaseCurrency: interest.toNumber(), liabilitiesInBaseCurrency: liabilities.toNumber(), totalInvestment: totalInvestment.toNumber(), totalValueInBaseCurrency: netWorth @@ -2126,11 +2126,11 @@ export class PortfolioService { .filter(({ isDraft, type }) => { return isDraft === false && type === activityType; }) - .map(({ quantity, SymbolProfile, unitPrice }) => { + .map(({ currency, quantity, SymbolProfile, unitPrice }) => { return new Big( this.exchangeRateDataService.toCurrency( new Big(quantity).mul(unitPrice).toNumber(), - SymbolProfile.currency, + currency ?? SymbolProfile.currency, userCurrency ) ); diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts index d7caf9bc9..433490325 100644 --- a/apps/api/src/helper/object.helper.spec.ts +++ b/apps/api/src/helper/object.helper.spec.ts @@ -1536,7 +1536,7 @@ describe('redactAttributes', () => { fireWealth: null, grossPerformance: null, grossPerformanceWithCurrencyEffect: null, - interest: null, + interestInBaseCurrency: null, items: null, liabilities: null, totalInvestment: null, @@ -3039,7 +3039,7 @@ describe('redactAttributes', () => { fireWealth: null, grossPerformance: null, grossPerformanceWithCurrencyEffect: null, - interest: null, + interestInBaseCurrency: null, items: null, liabilities: null, totalInvestment: null, diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app-routing.module.ts index 0e5a2dead..fb045a174 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app-routing.module.ts @@ -48,7 +48,7 @@ const routes: Routes = [ { path: publicRoutes.blog.path, loadChildren: () => - import('./pages/blog/blog-page.module').then((m) => m.BlogPageModule) + import('./pages/blog/blog-page.routes').then((m) => m.routes) }, { canActivate: [AuthGuard], @@ -155,8 +155,9 @@ const routes: Routes = [ // Preload all lazy loaded modules with the attribute preload === true { anchorScrolling: 'enabled', - preloadingStrategy: ModulePreloadService - // enableTracing: true // <-- debugging purposes only + // enableTracing: true, // <-- debugging purposes only + preloadingStrategy: ModulePreloadService, + scrollPositionRestoration: 'top' } ) ], diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html index c8d710019..b20b6b263 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html @@ -302,7 +302,7 @@ [isCurrency]="true" [locale]="locale" [unit]="baseCurrency" - [value]="isLoading ? undefined : summary?.interest" + [value]="isLoading ? undefined : summary?.interestInBaseCurrency" /> diff --git a/apps/client/src/app/pages/blog/blog-page.component.ts b/apps/client/src/app/pages/blog/blog-page.component.ts index 65a867f65..7599a3358 100644 --- a/apps/client/src/app/pages/blog/blog-page.component.ts +++ b/apps/client/src/app/pages/blog/blog-page.component.ts @@ -1,19 +1,24 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { Component, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Component, CUSTOM_ELEMENTS_SCHEMA, OnDestroy } 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' }, + imports: [CommonModule, IonIcon, MatCardModule, RouterModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-blog-page', styleUrls: ['./blog-page.scss'], - templateUrl: './blog-page.html', - standalone: false + templateUrl: './blog-page.html' }) -export class BlogPageComponent implements OnDestroy { +export class GfBlogPageComponent implements OnDestroy { public hasPermissionForSubscription: boolean; private unsubscribeSubject = new Subject(); diff --git a/apps/client/src/app/pages/blog/blog-page.module.ts b/apps/client/src/app/pages/blog/blog-page.module.ts deleted file mode 100644 index 37925e494..000000000 --- a/apps/client/src/app/pages/blog/blog-page.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { MatCardModule } from '@angular/material/card'; -import { IonIcon } from '@ionic/angular/standalone'; - -import { BlogPageRoutingModule } from './blog-page-routing.module'; -import { BlogPageComponent } from './blog-page.component'; - -@NgModule({ - declarations: [BlogPageComponent], - imports: [BlogPageRoutingModule, CommonModule, IonIcon, MatCardModule], - schemas: [CUSTOM_ELEMENTS_SCHEMA] -}) -export class BlogPageModule {} diff --git a/apps/client/src/app/pages/blog/blog-page-routing.module.ts b/apps/client/src/app/pages/blog/blog-page.routes.ts similarity index 95% rename from apps/client/src/app/pages/blog/blog-page-routing.module.ts rename to apps/client/src/app/pages/blog/blog-page.routes.ts index 9b352b7a8..2b5a4be64 100644 --- a/apps/client/src/app/pages/blog/blog-page-routing.module.ts +++ b/apps/client/src/app/pages/blog/blog-page.routes.ts @@ -1,15 +1,14 @@ import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Routes } from '@angular/router'; -import { BlogPageComponent } from './blog-page.component'; +import { GfBlogPageComponent } from './blog-page.component'; -const routes: Routes = [ +export const routes: Routes = [ { canActivate: [AuthGuard], - component: BlogPageComponent, + component: GfBlogPageComponent, path: '', title: publicRoutes.blog.title }, @@ -212,9 +211,3 @@ const routes: Routes = [ title: 'Hacktoberfest 2025' } ]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class BlogPageRoutingModule {} diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 170d70914..8bc3e3a67 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -69,6 +69,16 @@ export class GfPricingPageComponent implements OnDestroy, OnInit { public professionalDataProviderTooltipPremium = translate( 'PROFESSIONAL_DATA_PROVIDER_TOOLTIP_PREMIUM' ); + public referralBrokers = [ + 'DEGIRO', + 'finpension', + 'frankly', + 'Interactive Brokers', + 'Mintos', + 'Swissquote', + 'VIAC', + 'Zak' + ]; public routerLinkFeatures = publicRoutes.features.routerLink; public routerLinkRegister = publicRoutes.register.routerLink; public user: User; diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index ea68b74eb..ee006b2d6 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -326,16 +326,43 @@

- If you plan to open an account at DEGIRO, finpension, - frankly, Interactive Brokers, Swissquote, - VIAC, or Zak, please - If you plan to open an account at +   + @for ( + broker of referralBrokers; + track broker; + let i = $index; + let last = $last + ) { + {{ broker }} + @if (last) { + , + } @else { + @if (i === referralBrokers.length - 2) { +   + or +   + } @else { + , + } + } + } + please +   + contact us - to use our referral link and get a Ghostfolio Premium membership for - one year. Looking for a student discount? Request it - here - with your university e-mail address. +   + to use our referral link and get a Ghostfolio Premium membership + for one year. Looking for a student discount? +   + Request it +   + here +   + with your university e-mail address.

diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html index 123b4dac9..b028734a7 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html @@ -132,6 +132,23 @@ +
+
+

Stealth Wealth

+
+ Stealth wealth is a lifestyle choice where you don’t openly show + off your wealth, but instead live quietly to maintain privacy and + security. +
+ +
+
diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index c2678924b..0b5c4b253 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -42,7 +42,7 @@ import { MarketDataOfMarketsResponse, OAuthResponse, PortfolioDetails, - PortfolioDividends, + PortfolioDividendsResponse, PortfolioHoldingResponse, PortfolioHoldingsResponse, PortfolioInvestments, @@ -270,9 +270,12 @@ export class DataService { params = params.append('groupBy', groupBy); params = params.append('range', range); - return this.http.get('/api/v1/portfolio/dividends', { - params - }); + return this.http.get( + '/api/v1/portfolio/dividends', + { + params + } + ); } public fetchDividendsImport({ dataSource, symbol }: AssetProfileIdentifier) { diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 1da2236e8..d38502945 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -30,7 +30,6 @@ import type { LookupItem } from './lookup-item.interface'; import type { MarketData } from './market-data.interface'; import type { PortfolioChart } from './portfolio-chart.interface'; import type { PortfolioDetails } from './portfolio-details.interface'; -import type { PortfolioDividends } from './portfolio-dividends.interface'; import type { PortfolioInvestments } from './portfolio-investments.interface'; import type { PortfolioPerformance } from './portfolio-performance.interface'; import type { PortfolioPosition } from './portfolio-position.interface'; @@ -56,6 +55,7 @@ import type { LookupResponse } from './responses/lookup-response.interface'; import type { MarketDataDetailsResponse } from './responses/market-data-details-response.interface'; import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; +import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface'; import { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface'; import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; @@ -122,7 +122,7 @@ export { OAuthResponse, PortfolioChart, PortfolioDetails, - PortfolioDividends, + PortfolioDividendsResponse, PortfolioHoldingResponse, PortfolioHoldingsResponse, PortfolioInvestments, diff --git a/libs/common/src/lib/interfaces/portfolio-dividends.interface.ts b/libs/common/src/lib/interfaces/portfolio-dividends.interface.ts deleted file mode 100644 index 585c46bb7..000000000 --- a/libs/common/src/lib/interfaces/portfolio-dividends.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { InvestmentItem } from './investment-item.interface'; - -export interface PortfolioDividends { - dividends: InvestmentItem[]; -} diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index 092a4bb97..f08eb61b8 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -20,7 +20,7 @@ export interface PortfolioSummary extends PortfolioPerformance { fireWealth: FireWealth; grossPerformance: number; grossPerformanceWithCurrencyEffect: number; - interest: number; + interestInBaseCurrency: number; liabilitiesInBaseCurrency: number; totalBuy: number; totalSell: number; diff --git a/libs/common/src/lib/interfaces/responses/portfolio-dividends-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-dividends-response.interface.ts new file mode 100644 index 000000000..bd33dbccb --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/portfolio-dividends-response.interface.ts @@ -0,0 +1,5 @@ +import { InvestmentItem } from '../investment-item.interface'; + +export interface PortfolioDividendsResponse { + dividends: InvestmentItem[]; +} diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 472c24e2b..8079a6258 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -361,7 +361,11 @@ } - + @if (hasPermissionToCreateActivity) { } - +