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 dd2925f431..0000000000
--- 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 dfe024a035..0000000000
--- 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/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts
index 0d9743bdb5..27f60d86ac 100644
--- a/apps/client/src/app/components/markets/markets.component.ts
+++ b/apps/client/src/app/components/markets/markets.component.ts
@@ -1,4 +1,3 @@
-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 {
@@ -12,6 +11,7 @@ import {
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';
@@ -64,6 +64,7 @@ export class GfMarketsComponent implements OnInit {
protected hasPermissionToAccessFearAndGreedIndex: boolean;
protected hasPermissionToReadMarketDataOfMarkets: boolean;
protected historicalDataItems: HistoricalDataItem[];
+ protected isLoadingFearAndGreedIndex = true;
protected user: User;
private fearAndGreedIndexData: MarketDataOfMarketsResponse['fearAndGreedIndex'];
@@ -104,6 +105,8 @@ export class GfMarketsComponent implements OnInit {
!this.fearAndGreedIndexData
) {
this.fetchMarketDataOfMarkets();
+ } else {
+ this.isLoadingFearAndGreedIndex = false;
}
this.changeDetectorRef.markForCheck();
@@ -137,6 +140,8 @@ export class GfMarketsComponent implements OnInit {
this.initializeFearAndGreedIndex();
+ this.isLoadingFearAndGreedIndex = false;
+
this.changeDetectorRef.markForCheck();
});
}
diff --git a/apps/client/src/app/components/markets/markets.html b/apps/client/src/app/components/markets/markets.html
index 5e802d1ab0..bd9013a00e 100644
--- a/apps/client/src/app/components/markets/markets.html
+++ b/apps/client/src/app/components/markets/markets.html
@@ -41,6 +41,7 @@
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 5568902710..8b80370e8d 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/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts
index 5d1ff538e5..b2a3ab419a 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/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 0000000000..e441c53f6b
--- /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/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss
new file mode 100644
index 0000000000..5d4e87f30f
--- /dev/null
+++ b/libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss
@@ -0,0 +1,3 @@
+:host {
+ display: block;
+}
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 0000000000..b5b2074da9
--- /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 b507f0008b..4416752b37 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 0000000000..0f1db159ba
--- /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 26091be054..9e55a5edbf 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`,