Browse Source

Consolidate markets pages

pull/7471/head
Thomas Kaul 1 month ago
parent
commit
411d06c69e
  1. 5
      apps/api/src/app/user/user.service.ts
  2. 80
      apps/client/src/app/components/home-market/home-market.component.ts
  3. 35
      apps/client/src/app/components/home-market/home-market.html
  4. 3
      apps/client/src/app/components/home-market/home-market.scss
  5. 115
      apps/client/src/app/components/markets/markets.component.ts
  6. 19
      apps/client/src/app/components/markets/markets.html
  7. 15
      apps/client/src/app/pages/home/home-page.component.ts
  8. 8
      apps/client/src/app/pages/home/home-page.routes.ts
  9. 4
      apps/client/src/app/pages/markets/markets-page.component.ts
  10. 2
      apps/client/src/app/pages/markets/markets-page.html
  11. 5
      libs/common/src/lib/routes/routes.ts

5
apps/api/src/app/user/user.service.ts

@ -555,7 +555,10 @@ export class UserService {
}
} else {
if (
await this.propertyService.getByKey<string>(PROPERTY_API_KEY_GHOSTFOLIO)
this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX') ||
(await this.propertyService.getByKey<string>(
PROPERTY_API_KEY_GHOSTFOLIO
))
) {
currentPermissions.push(permissions.readMarketDataOfMarkets);
}

80
apps/client/src/app/components/home-market/home-market.component.ts

@ -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<Benchmark[]>([]);
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);
});
}
}

35
apps/client/src/app/components/home-market/home-market.html

@ -1,35 +0,0 @@
<div class="container">
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1>
@if (hasPermissionToAccessFearAndGreedIndex) {
<div class="mb-4 row">
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-fear-and-greed-index
class="d-flex justify-content-center"
[fearAndGreedIndex]="fearAndGreedIndex"
/>
</div>
</div>
}
<div class="mb-3 row">
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-benchmark
[benchmarks]="benchmarks()"
[deviceType]="deviceType()"
[locale]="user?.settings?.locale || undefined"
[showSymbol]="false"
[user]="user"
/>
@if (benchmarks()?.length > 0) {
<div
class="gf-text-wrap-balance line-height-1 mt-3 text-center text-muted"
>
<small i18n>
Calculations are based on delayed market data and may not be
displayed in real-time.</small
>
</div>
}
</div>
</div>
</div>

3
apps/client/src/app/components/home-market/home-market.scss

@ -1,3 +0,0 @@
:host {
display: block;
}

115
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<Benchmark[]>([]);
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();
}
}

19
apps/client/src/app/components/markets/markets.html

@ -1,9 +1,16 @@
<div class="container">
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1>
<div class="mb-5 row">
@if (
hasPermissionToAccessFearAndGreedIndex ||
hasPermissionToReadMarketDataOfMarkets
) {
<div class="mb-4 row">
<div class="col-xs-12 col-md-10 offset-md-1">
@if (hasPermissionToReadMarketDataOfMarkets) {
@if (user?.settings?.isExperimentalFeatures) {
<div class="align-items-center d-flex flex-grow-1 justify-content-end">
<div
class="align-items-center d-flex flex-grow-1 justify-content-end"
>
<gf-toggle
class="d-none d-lg-block"
[defaultValue]="fearAndGreedIndexMode"
@ -30,23 +37,25 @@
[yMin]="0"
[yMinLabel]="fearLabel"
/>
}
<gf-fear-and-greed-index
class="d-flex justify-content-center"
[fearAndGreedIndex]="fearAndGreedIndex"
/>
</div>
</div>
}
<div class="mb-3 row">
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-benchmark
[benchmarks]="benchmarks"
[deviceType]="deviceType"
[benchmarks]="benchmarks()"
[deviceType]="deviceType()"
[locale]="user?.settings?.locale || undefined"
[showSymbol]="false"
[user]="user"
/>
@if (benchmarks?.length > 0) {
@if (benchmarks()?.length > 0) {
<div
class="gf-text-wrap-balance line-height-1 mt-3 text-center text-muted"
>

15
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
}
];
}

8
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,

4
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'

2
apps/client/src/app/pages/markets/markets-page.html

@ -1,7 +1,7 @@
<div class="container">
<div class="row">
<div class="col">
<gf-home-market />
<gf-markets />
</div>
</div>
</div>

5
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'],

Loading…
Cancel
Save