Feature/add markets to public pages (#1062)

* Add Markets to public pages

* Update changelog
pull/1063/head
Thomas Kaul 2 years ago committed by GitHub
parent fd2408dd62
commit 2060fcaf0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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`

@ -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<BenchmarkResponse> {

@ -63,6 +63,8 @@ export class InfoService {
} else {
info.fearAndGreedDataSource = ghostfolioFearAndGreedIndexDataSource;
}
globalPermissions.push(permissions.enableFearAndGreedIndex);
}
if (this.configurationService.get('ENABLE_FEATURE_IMPORT')) {

@ -46,7 +46,6 @@ export class SymbolController {
* Must be after /lookup
*/
@Get(':dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getSymbolData(

@ -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);
}

@ -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: () =>

@ -269,6 +269,18 @@
[routerLink]="['/pricing']"
>Pricing</a
>
<a
*ngIf="hasPermissionToAccessFearAndGreedIndex"
class="d-none d-sm-block mx-1"
i18n
mat-flat-button
[ngClass]="{
'font-weight-bold': currentRoute === 'markets',
'text-decoration-underline': currentRoute === 'markets'
}"
[routerLink]="['/markets']"
>Markets</a
>
<a
class="d-none d-sm-block mx-1 no-min-width px-1"
href="https://github.com/ghostfolio/ghostfolio"

@ -37,6 +37,7 @@ export class HeaderComponent implements OnChanges {
public hasPermissionForSocialLogin: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessAdminControl: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public impersonationId: string;
public isMenuOpen: boolean;
@ -73,6 +74,11 @@ export class HeaderComponent implements OnChanges {
this.user?.permissions,
permissions.accessAdminControl
);
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
}
public impersonateAccount(aId: string) {

@ -44,50 +44,50 @@ export class HomeMarketComponent implements OnDestroy, OnInit {
if (state?.user) {
this.user = state.user;
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.user.permissions,
permissions.accessFearAndGreedIndex
);
this.changeDetectorRef.markForCheck();
}
});
}
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.isLoading = false;
public ngOnInit() {
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
this.changeDetectorRef.markForCheck();
});
}
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.dataService
.fetchBenchmarks()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ benchmarks }) => {
this.benchmarks = benchmarks;
this.changeDetectorRef.markForCheck();
});
}
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchBenchmarks()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ benchmarks }) => {
this.benchmarks = benchmarks;
this.isLoading = false;
this.changeDetectorRef.markForCheck();
}
this.changeDetectorRef.markForCheck();
});
}
public ngOnInit() {}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

@ -28,16 +28,17 @@
<div class="mb-3 row">
<div class="col-xs-12 col-md-8 offset-md-2">
<gf-benchmark
*ngFor="let benchmark of benchmarks"
class="py-2"
[benchmark]="benchmark"
[benchmarks]="benchmarks"
[locale]="user?.settings?.locale"
></gf-benchmark>
<gf-benchmark
*ngIf="!benchmarks"
class="py-2"
[benchmark]="undefined"
></gf-benchmark>
<ngx-skeleton-loader
*ngIf="isLoading"
animation="pulse"
[theme]="{
height: '1.5rem',
width: '100%'
}"
></ngx-skeleton-loader>
</div>
</div>
</div>

@ -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 {}

@ -22,6 +22,7 @@ export class AuthGuard implements CanActivate {
'/demo',
'/en/blog',
'/features',
'/markets',
'/p',
'/pricing',
'/register',

@ -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) {

@ -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 {}

@ -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<void>();
public constructor() {}
public ngOnInit() {}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

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

@ -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 {}

@ -40,6 +40,10 @@
<loc>https://ghostfol.io/features</loc>
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/markets</loc>
<lastmod>2022-07-01T00:00:00+00:00</lastmod>
</url>
<url>
<loc>https://ghostfol.io/pricing</loc>
<lastmod>2022-07-01T00:00:00+00:00</lastmod>

@ -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',

@ -1,49 +1,50 @@
<div class="align-items-center d-flex">
<div *ngIf="benchmark?.name" class="flex-grow-1 text-truncate">
{{ benchmark.name }}
</div>
<div *ngIf="!benchmark?.name" class="flex-grow-1">
<ngx-skeleton-loader
animation="pulse"
[theme]="{
width: '67%'
}"
></ngx-skeleton-loader>
</div>
<gf-value
class="mx-2"
size="medium"
[isPercent]="true"
[locale]="locale"
[ngClass]="{
'text-danger':
benchmark?.performances?.allTimeHigh?.performancePercent < 0,
'text-success':
benchmark?.performances?.allTimeHigh?.performancePercent > 0
}"
[value]="
benchmark?.performances?.allTimeHigh?.performancePercent ?? undefined
"
></gf-value>
<div class="text-muted">
<small class="d-none d-sm-block text-nowrap" i18n>from All Time High</small
><small class="d-block d-sm-none text-nowrap" i18n>from ATH</small>
</div>
<div class="ml-2">
<div
*ngIf="benchmark?.marketCondition"
[title]="benchmark?.marketCondition"
>
{{ resolveMarketCondition(benchmark.marketCondition).emoji }}
</div>
<ngx-skeleton-loader
*ngIf="!benchmark?.marketCondition"
animation="pulse"
appearance="circle"
[theme]="{
height: '1rem',
width: '1rem'
}"
></ngx-skeleton-loader>
</div>
</div>
<table class="gf-table w-100">
<thead>
<tr class="mat-header-row">
<th class="mat-header-cell px-1 py-2" i18n>Index</th>
<th class="mat-header-cell px-1 py-2 text-right">
<span class="d-none d-sm-block text-nowrap" i18n
>Change from All Time High</span
>
<span class="d-block d-sm-none text-nowrap" i18n>from ATH</span>
</th>
<th class="mat-header-cell px-1 py-2 text-right" i18n></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let benchmark of benchmarks" class="mat-row">
<td class="mat-cell px-1 py-2">
<div class="d-flex align-items-center">
{{ benchmark.name }}
</div>
</td>
<td class="mat-cell px-1 py-2 text-right">
<gf-value
class="d-inline-block justify-content-end"
size="medium"
[isPercent]="true"
[locale]="locale"
[ngClass]="{
'text-danger':
benchmark?.performances?.allTimeHigh?.performancePercent < 0,
'text-success':
benchmark?.performances?.allTimeHigh?.performancePercent > 0
}"
[value]="
benchmark?.performances?.allTimeHigh?.performancePercent ??
undefined
"
></gf-value>
</td>
<td class="mat-cell px-1 py-2">
<div
*ngIf="benchmark?.marketCondition"
class="text-center"
[title]="benchmark?.marketCondition"
>
{{ resolveMarketCondition(benchmark.marketCondition).emoji }}
</div>
</td>
</tr>
</tbody>
</table>

@ -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;
}
}
}

@ -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() {

Loading…
Cancel
Save