diff --git a/CHANGELOG.md b/CHANGELOG.md index 273be189..c97693a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Added _Markets_ to the public pages + ### Changed - Upgraded `ngx-markdown` from version `13.0.0` to `14.0.1` diff --git a/apps/api/src/app/benchmark/benchmark.controller.ts b/apps/api/src/app/benchmark/benchmark.controller.ts index 86bbf9d3..d3dcb0bb 100644 --- a/apps/api/src/app/benchmark/benchmark.controller.ts +++ b/apps/api/src/app/benchmark/benchmark.controller.ts @@ -3,8 +3,7 @@ import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interc import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { PROPERTY_BENCHMARKS } from '@ghostfolio/common/config'; import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces'; -import { Controller, Get, UseGuards, UseInterceptors } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; +import { Controller, Get, UseInterceptors } from '@nestjs/common'; import { BenchmarkService } from './benchmark.service'; @@ -16,7 +15,6 @@ export class BenchmarkController { ) {} @Get() - @UseGuards(AuthGuard('jwt')) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getBenchmark(): Promise { diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 04651dd8..e1f1e56c 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -63,6 +63,8 @@ export class InfoService { } else { info.fearAndGreedDataSource = ghostfolioFearAndGreedIndexDataSource; } + + globalPermissions.push(permissions.enableFearAndGreedIndex); } if (this.configurationService.get('ENABLE_FEATURE_IMPORT')) { diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index 5b3c0f03..dd50e0de 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -46,7 +46,6 @@ export class SymbolController { * Must be after /lookup */ @Get(':dataSource/:symbol') - @UseGuards(AuthGuard('jwt')) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getSymbolData( diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index f3a0cab9..32716b45 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -158,10 +158,6 @@ export class UserService { let currentPermissions = getPermissions(user.role); - if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { - currentPermissions.push(permissions.accessFearAndGreedIndex); - } - if (user.subscription?.type === 'Premium') { currentPermissions.push(permissions.reportDataGlitch); } diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app-routing.module.ts index ebe39892..be8aa7b9 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app-routing.module.ts @@ -90,6 +90,13 @@ const routes: Routes = [ loadChildren: () => import('./pages/home/home-page.module').then((m) => m.HomePageModule) }, + { + path: 'markets', + loadChildren: () => + import('./pages/markets/markets-page.module').then( + (m) => m.MarketsPageModule + ) + }, { path: 'p', loadChildren: () => diff --git a/apps/client/src/app/components/header/header.component.html b/apps/client/src/app/components/header/header.component.html index 107ec00f..23967fd8 100644 --- a/apps/client/src/app/components/header/header.component.html +++ b/apps/client/src/app/components/header/header.component.html @@ -269,6 +269,18 @@ [routerLink]="['/pricing']" >Pricing + Markets { - this.fearAndGreedIndex = marketPrice; - this.historicalData = [ - ...historicalData, - { - date: resetHours(new Date()).toISOString(), - value: marketPrice - } - ]; - this.isLoading = false; - - this.changeDetectorRef.markForCheck(); - }); - } - - this.dataService - .fetchBenchmarks() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ benchmarks }) => { - this.benchmarks = benchmarks; - - this.changeDetectorRef.markForCheck(); - }); - this.changeDetectorRef.markForCheck(); } }); } - public ngOnInit() {} + public ngOnInit() { + this.hasPermissionToAccessFearAndGreedIndex = hasPermission( + this.info?.globalPermissions, + permissions.enableFearAndGreedIndex + ); + + if (this.hasPermissionToAccessFearAndGreedIndex) { + this.dataService + .fetchSymbolItem({ + dataSource: this.info.fearAndGreedDataSource, + includeHistoricalData: this.numberOfDays, + symbol: ghostfolioFearAndGreedIndexSymbol + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ historicalData, marketPrice }) => { + this.fearAndGreedIndex = marketPrice; + this.historicalData = [ + ...historicalData, + { + date: resetHours(new Date()).toISOString(), + value: marketPrice + } + ]; + + this.changeDetectorRef.markForCheck(); + }); + } + + this.dataService + .fetchBenchmarks() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ benchmarks }) => { + this.benchmarks = benchmarks; + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + }); + } public ngOnDestroy() { this.unsubscribeSubject.next(); diff --git a/apps/client/src/app/components/home-market/home-market.html b/apps/client/src/app/components/home-market/home-market.html index d509a641..e8cf52be 100644 --- a/apps/client/src/app/components/home-market/home-market.html +++ b/apps/client/src/app/components/home-market/home-market.html @@ -28,16 +28,17 @@
- +
diff --git a/apps/client/src/app/components/home-market/home-market.module.ts b/apps/client/src/app/components/home-market/home-market.module.ts index 2c831a22..5b266056 100644 --- a/apps/client/src/app/components/home-market/home-market.module.ts +++ b/apps/client/src/app/components/home-market/home-market.module.ts @@ -3,19 +3,20 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module'; import { GfBenchmarkModule } from '@ghostfolio/ui/benchmark/benchmark.module'; import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { HomeMarketComponent } from './home-market.component'; @NgModule({ declarations: [HomeMarketComponent], - exports: [], + exports: [HomeMarketComponent], imports: [ CommonModule, GfBenchmarkModule, GfFearAndGreedIndexModule, - GfLineChartModule + GfLineChartModule, + NgxSkeletonLoaderModule ], - providers: [], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class GfHomeMarketModule {} diff --git a/apps/client/src/app/core/auth.guard.ts b/apps/client/src/app/core/auth.guard.ts index 5ae210ac..781a076b 100644 --- a/apps/client/src/app/core/auth.guard.ts +++ b/apps/client/src/app/core/auth.guard.ts @@ -22,6 +22,7 @@ export class AuthGuard implements CanActivate { '/demo', '/en/blog', '/features', + '/markets', '/p', '/pricing', '/register', 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 3a4226f5..67e38f67 100644 --- a/apps/client/src/app/pages/home/home-page.component.ts +++ b/apps/client/src/app/pages/home/home-page.component.ts @@ -7,7 +7,7 @@ import { } from '@angular/core'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { User } from '@ghostfolio/common/interfaces'; +import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @@ -24,6 +24,7 @@ export class HomePageComponent implements OnDestroy, OnInit { public hasMessage: boolean; public hasPermissionToAccessFearAndGreedIndex: boolean; + public info: InfoItem; public tabs: { iconName: string; path: string }[] = []; public user: User; @@ -34,7 +35,7 @@ export class HomePageComponent implements OnDestroy, OnInit { private dataService: DataService, private userService: UserService ) { - const { systemMessage } = this.dataService.fetchInfo(); + this.info = this.dataService.fetchInfo(); this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) @@ -51,11 +52,11 @@ export class HomePageComponent implements OnDestroy, OnInit { hasPermission( this.user?.permissions, permissions.createUserAccount - ) || !!systemMessage; + ) || !!this.info.systemMessage; this.hasPermissionToAccessFearAndGreedIndex = hasPermission( - this.user.permissions, - permissions.accessFearAndGreedIndex + this.info?.globalPermissions, + permissions.enableFearAndGreedIndex ); if (this.hasPermissionToAccessFearAndGreedIndex) { diff --git a/apps/client/src/app/pages/markets/markets-page-routing.module.ts b/apps/client/src/app/pages/markets/markets-page-routing.module.ts new file mode 100644 index 00000000..80b50fea --- /dev/null +++ b/apps/client/src/app/pages/markets/markets-page-routing.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; + +import { MarketsPageComponent } from './markets-page.component'; + +const routes: Routes = [ + { path: '', component: MarketsPageComponent, canActivate: [AuthGuard] } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class MarketsPageRoutingModule {} diff --git a/apps/client/src/app/pages/markets/markets-page.component.ts b/apps/client/src/app/pages/markets/markets-page.component.ts new file mode 100644 index 00000000..10aa0909 --- /dev/null +++ b/apps/client/src/app/pages/markets/markets-page.component.ts @@ -0,0 +1,21 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; + +@Component({ + host: { class: 'page' }, + selector: 'gf-markets-page', + styleUrls: ['./markets-page.scss'], + templateUrl: './markets-page.html' +}) +export class MarketsPageComponent implements OnDestroy, OnInit { + private unsubscribeSubject = new Subject(); + + public constructor() {} + + public ngOnInit() {} + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } +} diff --git a/apps/client/src/app/pages/markets/markets-page.html b/apps/client/src/app/pages/markets/markets-page.html new file mode 100644 index 00000000..b52b89cf --- /dev/null +++ b/apps/client/src/app/pages/markets/markets-page.html @@ -0,0 +1,7 @@ +
+
+
+ +
+
+
diff --git a/apps/client/src/app/pages/markets/markets-page.module.ts b/apps/client/src/app/pages/markets/markets-page.module.ts new file mode 100644 index 00000000..e61c7ccf --- /dev/null +++ b/apps/client/src/app/pages/markets/markets-page.module.ts @@ -0,0 +1,13 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { GfHomeMarketModule } from '@ghostfolio/client/components/home-market/home-market.module'; + +import { MarketsPageRoutingModule } from './markets-page-routing.module'; +import { MarketsPageComponent } from './markets-page.component'; + +@NgModule({ + declarations: [MarketsPageComponent], + imports: [CommonModule, GfHomeMarketModule, MarketsPageRoutingModule], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class MarketsPageModule {} diff --git a/apps/client/src/app/pages/markets/markets-page.scss b/apps/client/src/app/pages/markets/markets-page.scss new file mode 100644 index 00000000..5d4e87f3 --- /dev/null +++ b/apps/client/src/app/pages/markets/markets-page.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/apps/client/src/assets/sitemap.xml b/apps/client/src/assets/sitemap.xml index b3ba2deb..fc35cf44 100644 --- a/apps/client/src/assets/sitemap.xml +++ b/apps/client/src/assets/sitemap.xml @@ -40,6 +40,10 @@ https://ghostfol.io/features 2022-07-01T00:00:00+00:00 + + https://ghostfol.io/markets + 2022-07-01T00:00:00+00:00 + https://ghostfol.io/pricing 2022-07-01T00:00:00+00:00 diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index da666e4e..6be7a340 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -4,7 +4,6 @@ import { UserWithSettings } from './interfaces'; export const permissions = { accessAdminControl: 'accessAdminControl', - accessFearAndGreedIndex: 'accessFearAndGreedIndex', createAccess: 'createAccess', createAccount: 'createAccount', createOrder: 'createOrder', @@ -14,6 +13,7 @@ export const permissions = { deleteAuthDevice: 'deleteAuthDevice', deleteOrder: 'deleteOrder', deleteUser: 'deleteUser', + enableFearAndGreedIndex: 'enableFearAndGreedIndex', enableImport: 'enableImport', enableBlog: 'enableBlog', enableSocialLogin: 'enableSocialLogin', diff --git a/libs/ui/src/lib/benchmark/benchmark.component.html b/libs/ui/src/lib/benchmark/benchmark.component.html index 59113927..a0fa4c1c 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.html +++ b/libs/ui/src/lib/benchmark/benchmark.component.html @@ -1,49 +1,50 @@ -
-
- {{ benchmark.name }} -
-
- -
- -
- from All Time Highfrom ATH -
-
-
- {{ resolveMarketCondition(benchmark.marketCondition).emoji }} -
- -
-
+ + + + + + + + + + + + + + + +
Index + Change from All Time High + from ATH +
+
+ {{ benchmark.name }} +
+
+ + +
+ {{ resolveMarketCondition(benchmark.marketCondition).emoji }} +
+
diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index 939e3a35..c1e35f43 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -1,4 +1,10 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + Input, + OnChanges +} from '@angular/core'; +import { locale } from '@ghostfolio/common/config'; import { resolveMarketCondition } from '@ghostfolio/common/helper'; import { Benchmark } from '@ghostfolio/common/interfaces'; @@ -8,11 +14,17 @@ import { Benchmark } from '@ghostfolio/common/interfaces'; templateUrl: './benchmark.component.html', styleUrls: ['./benchmark.component.scss'] }) -export class BenchmarkComponent { - @Input() benchmark: Benchmark; +export class BenchmarkComponent implements OnChanges { + @Input() benchmarks: Benchmark[]; @Input() locale: string; public resolveMarketCondition = resolveMarketCondition; public constructor() {} + + public ngOnChanges() { + if (!this.locale) { + this.locale = locale; + } + } } diff --git a/libs/ui/src/lib/line-chart/line-chart.component.ts b/libs/ui/src/lib/line-chart/line-chart.component.ts index bcf004ed..2d65de50 100644 --- a/libs/ui/src/lib/line-chart/line-chart.component.ts +++ b/libs/ui/src/lib/line-chart/line-chart.component.ts @@ -15,7 +15,11 @@ import { getTooltipPositionerMapTop, getVerticalHoverLinePlugin } from '@ghostfolio/common/chart-helper'; -import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config'; +import { + locale, + primaryColorRgb, + secondaryColorRgb +} from '@ghostfolio/common/config'; import { getBackgroundColor, getDateFormatString, @@ -97,6 +101,10 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy { this.changeDetectorRef.markForCheck(); }); } + + if (!this.locale) { + this.locale = locale; + } } public ngOnDestroy() {