From 411d06c69e2516bdc97f5e56341a528f9c5f7b5e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:08:22 +0200 Subject: [PATCH] Consolidate markets pages --- apps/api/src/app/user/user.service.ts | 5 +- .../home-market/home-market.component.ts | 80 ------------ .../components/home-market/home-market.html | 35 ------ .../components/home-market/home-market.scss | 3 - .../components/markets/markets.component.ts | 115 ++++++++++++------ .../src/app/components/markets/markets.html | 81 ++++++------ .../src/app/pages/home/home-page.component.ts | 15 +-- .../src/app/pages/home/home-page.routes.ts | 8 +- .../pages/markets/markets-page.component.ts | 4 +- .../src/app/pages/markets/markets-page.html | 2 +- libs/common/src/lib/routes/routes.ts | 5 - 11 files changed, 131 insertions(+), 222 deletions(-) delete mode 100644 apps/client/src/app/components/home-market/home-market.component.ts delete mode 100644 apps/client/src/app/components/home-market/home-market.html delete mode 100644 apps/client/src/app/components/home-market/home-market.scss diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 648f36cbf5..7f3631c54d 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -555,7 +555,10 @@ export class UserService { } } else { if ( - await this.propertyService.getByKey(PROPERTY_API_KEY_GHOSTFOLIO) + this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX') || + (await this.propertyService.getByKey( + PROPERTY_API_KEY_GHOSTFOLIO + )) ) { currentPermissions.push(permissions.readMarketDataOfMarkets); } diff --git a/apps/client/src/app/components/home-market/home-market.component.ts b/apps/client/src/app/components/home-market/home-market.component.ts deleted file mode 100644 index 0eec3f2d9e..0000000000 --- a/apps/client/src/app/components/home-market/home-market.component.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component'; -import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { Benchmark, InfoItem, User } from '@ghostfolio/common/interfaces'; -import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; -import { DataService } from '@ghostfolio/ui/services'; - -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - computed, - CUSTOM_ELEMENTS_SCHEMA, - DestroyRef, - inject, - OnInit, - signal -} from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { DeviceDetectorService } from 'ngx-device-detector'; - -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [GfBenchmarkComponent, GfFearAndGreedIndexComponent], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - selector: 'gf-home-market', - styleUrls: ['./home-market.scss'], - templateUrl: './home-market.html' -}) -export class GfHomeMarketComponent implements OnInit { - protected readonly benchmarks = signal([]); - - protected readonly deviceType = computed( - () => this.deviceDetectorService.deviceInfo().deviceType - ); - - protected fearAndGreedIndex: number | undefined; - protected hasPermissionToAccessFearAndGreedIndex: boolean; - protected user: User; - - private readonly info: InfoItem; - - private readonly changeDetectorRef = inject(ChangeDetectorRef); - private readonly dataService = inject(DataService); - private readonly destroyRef = inject(DestroyRef); - private readonly deviceDetectorService = inject(DeviceDetectorService); - private readonly userService = inject(UserService); - - public constructor() { - this.info = this.dataService.fetchInfo(); - - this.userService.stateChanged - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((state) => { - if (state?.user) { - this.user = state.user; - - this.changeDetectorRef.markForCheck(); - } - }); - } - - public ngOnInit() { - this.hasPermissionToAccessFearAndGreedIndex = hasPermission( - this.info?.globalPermissions, - permissions.enableFearAndGreedIndex - ); - - if (this.hasPermissionToAccessFearAndGreedIndex) { - this.fearAndGreedIndex = this.info.fearAndGreedStocksMarketPrice; - } - - this.dataService - .fetchBenchmarks() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ benchmarks }) => { - this.benchmarks.set(benchmarks); - }); - } -} diff --git a/apps/client/src/app/components/home-market/home-market.html b/apps/client/src/app/components/home-market/home-market.html deleted file mode 100644 index b1e21df955..0000000000 --- a/apps/client/src/app/components/home-market/home-market.html +++ /dev/null @@ -1,35 +0,0 @@ -
-

Markets

- @if (hasPermissionToAccessFearAndGreedIndex) { -
-
- -
-
- } - -
-
- - @if (benchmarks()?.length > 0) { -
- - Calculations are based on delayed market data and may not be - displayed in real-time. -
- } -
-
-
diff --git a/apps/client/src/app/components/home-market/home-market.scss b/apps/client/src/app/components/home-market/home-market.scss deleted file mode 100644 index 5d4e87f30f..0000000000 --- a/apps/client/src/app/components/home-market/home-market.scss +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: block; -} diff --git a/apps/client/src/app/components/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index d2f64f3fce..0d9743bdb5 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -4,10 +4,12 @@ import { resetHours } from '@ghostfolio/common/helper'; import { Benchmark, HistoricalDataItem, + InfoItem, MarketDataOfMarketsResponse, ToggleOption, User } from '@ghostfolio/common/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { FearAndGreedIndexMode } from '@ghostfolio/common/types'; import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; @@ -18,9 +20,12 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + computed, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, - OnInit + inject, + OnInit, + signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DeviceDetectorService } from 'ngx-device-detector'; @@ -39,29 +44,49 @@ import { DeviceDetectorService } from 'ngx-device-detector'; templateUrl: './markets.html' }) export class GfMarketsComponent implements OnInit { - public benchmarks: Benchmark[]; - public deviceType: string; - public fearAndGreedIndex: number; - public fearAndGreedIndexData: MarketDataOfMarketsResponse['fearAndGreedIndex']; - public fearLabel = $localize`Fear`; - public greedLabel = $localize`Greed`; - public historicalDataItems: HistoricalDataItem[]; - public fearAndGreedIndexMode: FearAndGreedIndexMode = 'STOCKS'; - public fearAndGreedIndexModeOptions: ToggleOption[] = [ + protected readonly benchmarks = signal([]); + + protected readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + + protected readonly fearAndGreedIndexModeOptions: ToggleOption[] = [ { label: $localize`Stocks`, value: 'STOCKS' }, { label: $localize`Cryptocurrencies`, value: 'CRYPTOCURRENCIES' } ]; - public readonly numberOfDays = 365; - public user: User; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private userService: UserService - ) { - this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; + + protected readonly fearLabel = $localize`Fear`; + protected readonly greedLabel = $localize`Greed`; + protected readonly numberOfDays = 365; + + protected fearAndGreedIndex: number | undefined; + protected fearAndGreedIndexMode: FearAndGreedIndexMode = 'STOCKS'; + protected hasPermissionToAccessFearAndGreedIndex: boolean; + protected hasPermissionToReadMarketDataOfMarkets: boolean; + protected historicalDataItems: HistoricalDataItem[]; + protected user: User; + + private fearAndGreedIndexData: MarketDataOfMarketsResponse['fearAndGreedIndex']; + + private readonly info: InfoItem; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly userService = inject(UserService); + + public constructor() { + this.info = this.dataService.fetchInfo(); + + this.hasPermissionToAccessFearAndGreedIndex = hasPermission( + this.info?.globalPermissions, + permissions.enableFearAndGreedIndex + ); + + if (this.hasPermissionToAccessFearAndGreedIndex) { + this.fearAndGreedIndex = this.info.fearAndGreedStocksMarketPrice; + } this.userService.stateChanged .pipe(takeUntilDestroyed(this.destroyRef)) @@ -69,6 +94,18 @@ export class GfMarketsComponent implements OnInit { if (state?.user) { this.user = state.user; + this.hasPermissionToReadMarketDataOfMarkets = hasPermission( + this.user.permissions, + permissions.readMarketDataOfMarkets + ); + + if ( + this.hasPermissionToReadMarketDataOfMarkets && + !this.fearAndGreedIndexData + ) { + this.fetchMarketDataOfMarkets(); + } + this.changeDetectorRef.markForCheck(); } }); @@ -76,27 +113,35 @@ export class GfMarketsComponent implements OnInit { public ngOnInit() { this.dataService - .fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays }) + .fetchBenchmarks() .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ fearAndGreedIndex }) => { - this.fearAndGreedIndexData = fearAndGreedIndex; + .subscribe(({ benchmarks }) => { + this.benchmarks.set(benchmarks); + }); + } - this.initialize(); + protected onChangeFearAndGreedIndexMode( + aFearAndGreedIndexMode: FearAndGreedIndexMode + ) { + this.fearAndGreedIndexMode = aFearAndGreedIndexMode; - this.changeDetectorRef.markForCheck(); - }); + this.initializeFearAndGreedIndex(); + } + private fetchMarketDataOfMarkets() { this.dataService - .fetchBenchmarks() + .fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays }) .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(({ benchmarks }) => { - this.benchmarks = benchmarks; + .subscribe(({ fearAndGreedIndex }) => { + this.fearAndGreedIndexData = fearAndGreedIndex; + + this.initializeFearAndGreedIndex(); this.changeDetectorRef.markForCheck(); }); } - public initialize() { + private initializeFearAndGreedIndex() { this.fearAndGreedIndex = this.fearAndGreedIndexData[this.fearAndGreedIndexMode]?.marketPrice; @@ -109,12 +154,4 @@ export class GfMarketsComponent implements OnInit { } ]; } - - public onChangeFearAndGreedIndexMode( - aFearAndGreedIndexMode: FearAndGreedIndexMode - ) { - this.fearAndGreedIndexMode = aFearAndGreedIndexMode; - - this.initialize(); - } } diff --git a/apps/client/src/app/components/markets/markets.html b/apps/client/src/app/components/markets/markets.html index 38234a7850..5e802d1ab0 100644 --- a/apps/client/src/app/components/markets/markets.html +++ b/apps/client/src/app/components/markets/markets.html @@ -1,52 +1,61 @@

Markets

-
-
- @if (user?.settings?.isExperimentalFeatures) { -
- +
+ @if (hasPermissionToReadMarketDataOfMarkets) { + @if (user?.settings?.isExperimentalFeatures) { +
+ +
+ } +
+ Last {{ numberOfDays }} Days +
+ -
- } -
- Last {{ numberOfDays }} Days + } +
- -
-
+ }
- @if (benchmarks?.length > 0) { + @if (benchmarks()?.length > 0) {
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 574286c826..4ef7741def 100644 --- a/apps/client/src/app/pages/home/home-page.component.ts +++ b/apps/client/src/app/pages/home/home-page.component.ts @@ -1,7 +1,6 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { User } from '@ghostfolio/common/interfaces'; -import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { GfPageTabsComponent, @@ -73,18 +72,8 @@ export class GfHomePageComponent implements OnInit { }, { iconName: 'newspaper-outline', - label: hasPermission( - this.user?.permissions, - permissions.readMarketDataOfMarkets - ) - ? internalRoutes.home.subRoutes.marketsPremium.title - : internalRoutes.home.subRoutes.markets.title, - routerLink: hasPermission( - this.user?.permissions, - permissions.readMarketDataOfMarkets - ) - ? internalRoutes.home.subRoutes.marketsPremium.routerLink - : internalRoutes.home.subRoutes.markets.routerLink + label: internalRoutes.home.subRoutes.markets.title, + routerLink: internalRoutes.home.subRoutes.markets.routerLink } ]; } 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 82ef1e521f..cc444c2b00 100644 --- a/apps/client/src/app/pages/home/home-page.routes.ts +++ b/apps/client/src/app/pages/home/home-page.routes.ts @@ -1,5 +1,4 @@ import { GfHomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component'; -import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; import { GfHomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component'; import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component'; import { GfHomeWatchlistComponent } from '@ghostfolio/client/components/home-watchlist/home-watchlist.component'; @@ -31,13 +30,8 @@ export const routes: Routes = [ }, { path: internalRoutes.home.subRoutes.markets.path, - component: GfHomeMarketComponent, - title: internalRoutes.home.subRoutes.markets.title - }, - { - path: internalRoutes.home.subRoutes.marketsPremium.path, component: GfMarketsComponent, - title: internalRoutes.home.subRoutes.marketsPremium.title + title: internalRoutes.home.subRoutes.markets.title }, { path: internalRoutes.home.subRoutes.watchlist.path, diff --git a/apps/client/src/app/pages/markets/markets-page.component.ts b/apps/client/src/app/pages/markets/markets-page.component.ts index 9a7576a95d..bf35c5556c 100644 --- a/apps/client/src/app/pages/markets/markets-page.component.ts +++ b/apps/client/src/app/pages/markets/markets-page.component.ts @@ -1,11 +1,11 @@ -import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; +import { GfMarketsComponent } from '@ghostfolio/client/components/markets/markets.component'; import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, - imports: [GfHomeMarketComponent], + imports: [GfMarketsComponent], selector: 'gf-markets-page', styleUrls: ['./markets-page.scss'], templateUrl: './markets-page.html' diff --git a/apps/client/src/app/pages/markets/markets-page.html b/apps/client/src/app/pages/markets/markets-page.html index 16457b8cd5..0ff0170d7c 100644 --- a/apps/client/src/app/pages/markets/markets-page.html +++ b/apps/client/src/app/pages/markets/markets-page.html @@ -1,7 +1,7 @@
- +
diff --git a/libs/common/src/lib/routes/routes.ts b/libs/common/src/lib/routes/routes.ts index 86cb2480b8..4d40cc5fbd 100644 --- a/libs/common/src/lib/routes/routes.ts +++ b/libs/common/src/lib/routes/routes.ts @@ -94,11 +94,6 @@ export const internalRoutes = { routerLink: ['/home', 'markets'], title: $localize`Markets` }, - marketsPremium: { - path: 'markets-premium', - routerLink: ['/home', 'markets-premium'], - title: $localize`Markets` - }, summary: { path: 'summary', routerLink: ['/home', 'summary'],