From 52fa7c224046bb3b84f88af8071d900450e174c9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:06:00 +0200 Subject: [PATCH] Task/consolidate markets pages (#7471) * Consolidate markets pages * Update changelog --- CHANGELOG.md | 6 + apps/api/src/app/user/user.service.ts | 5 +- .../fear-and-greed-index.component.html | 24 ---- .../fear-and-greed-index.component.scss | 8 -- .../home-market/home-market.component.ts | 80 ------------ .../components/home-market/home-market.html | 35 ----- .../components/markets/markets.component.ts | 122 ++++++++++++------ .../src/app/components/markets/markets.html | 82 ++++++------ .../src/app/pages/api/api-page.component.ts | 2 +- apps/client/src/app/pages/api/api-page.html | 22 +--- .../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/helper.ts | 6 +- libs/common/src/lib/routes/routes.ts | 5 - .../fear-and-greed-index.component.html | 25 ++++ .../fear-and-greed-index.component.scss | 0 .../fear-and-greed-index.component.stories.ts | 39 ++++++ .../fear-and-greed-index.component.ts | 23 ++-- libs/ui/src/lib/fear-and-greed-index/index.ts | 1 + libs/ui/src/lib/i18n.ts | 1 + 22 files changed, 234 insertions(+), 281 deletions(-) delete mode 100644 apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html delete mode 100644 apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.scss 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 create mode 100644 libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.html rename apps/client/src/app/components/home-market/home-market.scss => libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss (100%) create mode 100644 libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.stories.ts rename {apps/client/src/app/components => libs/ui/src/lib}/fear-and-greed-index/fear-and-greed-index.component.ts (53%) create mode 100644 libs/ui/src/lib/fear-and-greed-index/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 500d00d2c..f5fefea4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added an empty state to the _Fear & Greed Index_ component +- Added a _Storybook_ story for the _Fear & Greed Index_ component + ### Changed - Moved the tags to the overview tab of the account detail dialog (experimental) - Moved the tags to the overview tab of the holding detail dialog +- Consolidated the markets pages into a single route where the _Fear & Greed Index_ is controlled by permission - Refactored the line chart components to share the common chart configuration - Improved the language localization for Spanish (`es`) - Improved the language localization for Ukrainian (`uk`) diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 648f36cbf..7f3631c54 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/fear-and-greed-index/fear-and-greed-index.component.html b/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html deleted file mode 100644 index dd2925f43..000000000 --- a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html +++ /dev/null @@ -1,24 +0,0 @@ -
-
-
{{ fearAndGreedIndexEmoji }}
-
-
- {{ fearAndGreedIndexText }} - {{ fearAndGreedIndex | number: '1.0-0' }}/100 -
- Current Market Mood -
-
- @if (!fearAndGreedIndex) { - - } -
diff --git a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.scss b/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.scss deleted file mode 100644 index dfe024a03..000000000 --- a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -:host { - display: block; - - ngx-skeleton-loader { - bottom: 0; - top: 0; - } -} 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 0eec3f2d9..000000000 --- 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 b1e21df95..000000000 --- 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/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index d2f64f3fc..27f60d86a 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -1,15 +1,17 @@ -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 { 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 { GfFearAndGreedIndexComponent } from '@ghostfolio/ui/fear-and-greed-index'; import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; import { DataService } from '@ghostfolio/ui/services'; import { GfToggleComponent } from '@ghostfolio/ui/toggle'; @@ -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,50 @@ 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 isLoadingFearAndGreedIndex = true; + 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 +95,20 @@ 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(); + } else { + this.isLoadingFearAndGreedIndex = false; + } + this.changeDetectorRef.markForCheck(); } }); @@ -76,27 +116,37 @@ 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.isLoadingFearAndGreedIndex = false; this.changeDetectorRef.markForCheck(); }); } - public initialize() { + private initializeFearAndGreedIndex() { this.fearAndGreedIndex = this.fearAndGreedIndexData[this.fearAndGreedIndexMode]?.marketPrice; @@ -109,12 +159,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 38234a785..bd9013a00 100644 --- a/apps/client/src/app/components/markets/markets.html +++ b/apps/client/src/app/components/markets/markets.html @@ -1,52 +1,62 @@

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/api/api-page.component.ts b/apps/client/src/app/pages/api/api-page.component.ts index 556890271..8b80370e8 100644 --- a/apps/client/src/app/pages/api/api-page.component.ts +++ b/apps/client/src/app/pages/api/api-page.component.ts @@ -1,4 +1,3 @@ -import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component'; import { HEADER_KEY_SKIP_INTERCEPTOR, HEADER_KEY_TOKEN @@ -14,6 +13,7 @@ import { MarketDataOfMarketsResponse, QuotesResponse } from '@ghostfolio/common/interfaces'; +import { GfFearAndGreedIndexComponent } from '@ghostfolio/ui/fear-and-greed-index'; import { CommonModule } from '@angular/common'; import { diff --git a/apps/client/src/app/pages/api/api-page.html b/apps/client/src/app/pages/api/api-page.html index b2d9d03ac..2153fba1e 100644 --- a/apps/client/src/app/pages/api/api-page.html +++ b/apps/client/src/app/pages/api/api-page.html @@ -179,16 +179,12 @@ @if (isFetchFailure(marketDataOfMarkets)) { 🔴 {{ marketDataOfMarkets.fetchError }} - } @else if (marketDataOfMarkets) { + } @else { - } @else { - } @@ -203,17 +199,13 @@ @if (isFetchFailure(marketDataOfMarkets)) { 🔴 {{ marketDataOfMarkets.fetchError }} - } @else if (marketDataOfMarkets) { + } @else { - } @else { - } 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 574286c82..4ef7741de 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 82ef1e521..cc444c2b0 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 9a7576a95..bf35c5556 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 16457b8cd..0ff0170d7 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/helper.ts b/libs/common/src/lib/helper.ts index 5d1ff538e..b2a3ab419 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -560,8 +560,10 @@ export function resetHours(aDate: Date) { return new Date(Date.UTC(year, month, day)); } -export function resolveFearAndGreedIndex(aValue: number) { - if (aValue <= 25) { +export function resolveFearAndGreedIndex(aValue?: number) { + if (isNil(aValue)) { + return { emoji: '⚪', key: 'UNKNOWN', text: 'Unknown' }; + } else if (aValue <= 25) { return { emoji: '🥵', key: 'EXTREME_FEAR', text: 'Extreme Fear' }; } else if (aValue <= 45) { return { emoji: '😨', key: 'FEAR', text: 'Fear' }; diff --git a/libs/common/src/lib/routes/routes.ts b/libs/common/src/lib/routes/routes.ts index 86cb2480b..4d40cc5fb 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'], diff --git a/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.html b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.html new file mode 100644 index 000000000..e441c53f6 --- /dev/null +++ b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.html @@ -0,0 +1,25 @@ +@if (isLoading() && !fearAndGreedIndex()) { + +} @else { +
+
{{ fearAndGreedIndexEmoji() }}
+
+
+ {{ fearAndGreedIndexText() }} + {{ + (fearAndGreedIndex() | number: '1.0-0') ?? placeholder + }}/100 +
+ Current Market Mood +
+
+} diff --git a/apps/client/src/app/components/home-market/home-market.scss b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss similarity index 100% rename from apps/client/src/app/components/home-market/home-market.scss rename to libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss diff --git a/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.stories.ts b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.stories.ts new file mode 100644 index 000000000..b5b2074da --- /dev/null +++ b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.stories.ts @@ -0,0 +1,39 @@ +import '@angular/localize/init'; +import { moduleMetadata } from '@storybook/angular'; +import type { Meta, StoryObj } from '@storybook/angular'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { GfFearAndGreedIndexComponent } from './fear-and-greed-index.component'; + +export default { + title: 'Fear & Greed Index', + component: GfFearAndGreedIndexComponent, + decorators: [ + moduleMetadata({ + imports: [NgxSkeletonLoaderModule] + }) + ] +} as Meta; + +type Story = StoryObj; + +export const Loading: Story = { + args: { + fearAndGreedIndex: undefined, + isLoading: true + } +}; + +export const Default: Story = { + args: { + fearAndGreedIndex: 50, + isLoading: false + } +}; + +export const Unknown: Story = { + args: { + fearAndGreedIndex: undefined, + isLoading: false + } +}; diff --git a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.ts similarity index 53% rename from apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts rename to libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.ts index b507f0008..4416752b3 100644 --- a/apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts +++ b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.ts @@ -5,8 +5,8 @@ import { DecimalPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, - Input, - OnChanges + computed, + input } from '@angular/core'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -17,16 +17,17 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; styleUrls: ['./fear-and-greed-index.component.scss'], templateUrl: './fear-and-greed-index.component.html' }) -export class GfFearAndGreedIndexComponent implements OnChanges { - @Input() fearAndGreedIndex: number; +export class GfFearAndGreedIndexComponent { + public readonly fearAndGreedIndex = input(); + public readonly isLoading = input(false); - public fearAndGreedIndexEmoji: string; - public fearAndGreedIndexText: string; + protected readonly placeholder = '—'; - public ngOnChanges() { - const { emoji, key } = resolveFearAndGreedIndex(this.fearAndGreedIndex); + protected readonly fearAndGreedIndexEmoji = computed(() => { + return resolveFearAndGreedIndex(this.fearAndGreedIndex()).emoji; + }); - this.fearAndGreedIndexEmoji = emoji; - this.fearAndGreedIndexText = translate(key); - } + protected readonly fearAndGreedIndexText = computed(() => { + return translate(resolveFearAndGreedIndex(this.fearAndGreedIndex()).key); + }); } diff --git a/libs/ui/src/lib/fear-and-greed-index/index.ts b/libs/ui/src/lib/fear-and-greed-index/index.ts new file mode 100644 index 000000000..0f1db159b --- /dev/null +++ b/libs/ui/src/lib/fear-and-greed-index/index.ts @@ -0,0 +1 @@ +export * from './fear-and-greed-index.component'; diff --git a/libs/ui/src/lib/i18n.ts b/libs/ui/src/lib/i18n.ts index 26091be05..9e55a5edb 100644 --- a/libs/ui/src/lib/i18n.ts +++ b/libs/ui/src/lib/i18n.ts @@ -85,6 +85,7 @@ const locales = { FEAR: $localize`Fear`, GREED: $localize`Greed`, NEUTRAL: $localize`Neutral`, + UNKNOWN: $localize`Unknown`, // Sectors 'Basic Materials': $localize`Basic Materials`,