Feature/add global heat map to landing page (#1584)
* Add global heat map * Update changelog
This commit is contained in:
parent
fc8e23a9c8
commit
90a7a84ac5
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Extended the landing page by a global heat map of subscribers
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved the form of the import dividends dialog (disable while loading)
|
- Improved the form of the import dividends dialog (disable while loading)
|
||||||
|
@ -7,6 +7,7 @@ import { PropertyService } from '@ghostfolio/api/services/property/property.serv
|
|||||||
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
||||||
import {
|
import {
|
||||||
DEMO_USER_ID,
|
DEMO_USER_ID,
|
||||||
|
PROPERTY_COUNTRIES_OF_SUBSCRIBERS,
|
||||||
PROPERTY_IS_READ_ONLY_MODE,
|
PROPERTY_IS_READ_ONLY_MODE,
|
||||||
PROPERTY_SLACK_COMMUNITY_USERS,
|
PROPERTY_SLACK_COMMUNITY_USERS,
|
||||||
PROPERTY_STRIPE_CONFIG,
|
PROPERTY_STRIPE_CONFIG,
|
||||||
@ -92,6 +93,10 @@ export class InfoService {
|
|||||||
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
||||||
globalPermissions.push(permissions.enableSubscription);
|
globalPermissions.push(permissions.enableSubscription);
|
||||||
|
|
||||||
|
info.countriesOfSubscribers =
|
||||||
|
((await this.propertyService.getByKey(
|
||||||
|
PROPERTY_COUNTRIES_OF_SUBSCRIBERS
|
||||||
|
)) as string[]) ?? [];
|
||||||
info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY');
|
info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ import svgMap from 'svgmap';
|
|||||||
styleUrls: ['./world-map-chart.component.scss']
|
styleUrls: ['./world-map-chart.component.scss']
|
||||||
})
|
})
|
||||||
export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit {
|
export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit {
|
||||||
@Input() baseCurrency: string;
|
@Input() countries: { [code: string]: { name?: string; value: number } };
|
||||||
@Input() countries: { [code: string]: { name: string; value: number } };
|
@Input() format: string;
|
||||||
@Input() isInPercent = false;
|
@Input() isInPercent = false;
|
||||||
|
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
@ -71,7 +71,7 @@ export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
applyData: 'value',
|
applyData: 'value',
|
||||||
data: {
|
data: {
|
||||||
value: {
|
value: {
|
||||||
format: this.isInPercent ? `{0}%` : `{0} ${this.baseCurrency}`
|
format: this.format
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
values: this.countries
|
values: this.countries
|
||||||
|
@ -13,10 +13,14 @@ import { Subject } from 'rxjs';
|
|||||||
templateUrl: './landing-page.html'
|
templateUrl: './landing-page.html'
|
||||||
})
|
})
|
||||||
export class LandingPageComponent implements OnDestroy, OnInit {
|
export class LandingPageComponent implements OnDestroy, OnInit {
|
||||||
|
public countriesOfSubscribersMap: {
|
||||||
|
[code: string]: { value: number };
|
||||||
|
} = {};
|
||||||
public currentYear = format(new Date(), 'yyyy');
|
public currentYear = format(new Date(), 'yyyy');
|
||||||
public demoAuthToken: string;
|
public demoAuthToken: string;
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasPermissionForStatistics: boolean;
|
public hasPermissionForStatistics: boolean;
|
||||||
|
public hasPermissionForSubscription: boolean;
|
||||||
public hasPermissionToCreateUser: boolean;
|
public hasPermissionToCreateUser: boolean;
|
||||||
public statistics: Statistics;
|
public statistics: Statistics;
|
||||||
public testimonials = [
|
public testimonials = [
|
||||||
@ -48,13 +52,25 @@ export class LandingPageComponent implements OnDestroy, OnInit {
|
|||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private deviceService: DeviceDetectorService
|
private deviceService: DeviceDetectorService
|
||||||
) {
|
) {
|
||||||
const { globalPermissions, statistics } = this.dataService.fetchInfo();
|
const { countriesOfSubscribers, globalPermissions, statistics } =
|
||||||
|
this.dataService.fetchInfo();
|
||||||
|
|
||||||
|
for (const country of countriesOfSubscribers) {
|
||||||
|
this.countriesOfSubscribersMap[country] = {
|
||||||
|
value: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.hasPermissionForStatistics = hasPermission(
|
this.hasPermissionForStatistics = hasPermission(
|
||||||
globalPermissions,
|
globalPermissions,
|
||||||
permissions.enableStatistics
|
permissions.enableStatistics
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.hasPermissionForSubscription = hasPermission(
|
||||||
|
globalPermissions,
|
||||||
|
permissions.enableSubscription
|
||||||
|
);
|
||||||
|
|
||||||
this.hasPermissionToCreateUser = hasPermission(
|
this.hasPermissionToCreateUser = hasPermission(
|
||||||
globalPermissions,
|
globalPermissions,
|
||||||
permissions.createUserAccount
|
permissions.createUserAccount
|
||||||
|
@ -269,6 +269,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="hasPermissionForSubscription" class="row my-5">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 class="h4 text-center">
|
||||||
|
Members from around the globe are using
|
||||||
|
<a href="pricing"><strong>Ghostfolio Premium</strong></a>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 customer-map-container offset-md-2">
|
||||||
|
<gf-world-map-chart
|
||||||
|
format="👻"
|
||||||
|
[countries]="countriesOfSubscribersMap"
|
||||||
|
></gf-world-map-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h2 class="h4 mb-1 text-center">
|
<h2 class="h4 mb-1 text-center">
|
||||||
|
@ -3,7 +3,9 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
|
||||||
import { GfLogoModule } from '@ghostfolio/ui/logo';
|
import { GfLogoModule } from '@ghostfolio/ui/logo';
|
||||||
|
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
|
||||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
import { LandingPageRoutingModule } from './landing-page-routing.module';
|
import { LandingPageRoutingModule } from './landing-page-routing.module';
|
||||||
@ -14,7 +16,9 @@ import { LandingPageComponent } from './landing-page.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfLogoModule,
|
GfLogoModule,
|
||||||
|
GfPremiumIndicatorModule,
|
||||||
GfValueModule,
|
GfValueModule,
|
||||||
|
GfWorldMapChartModule,
|
||||||
LandingPageRoutingModule,
|
LandingPageRoutingModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customer-map-container {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
|
||||||
.downloads {
|
.downloads {
|
||||||
img {
|
img {
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
|
@ -84,6 +84,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public user: User;
|
public user: User;
|
||||||
|
public worldMapChartFormat: string;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
@ -193,6 +194,11 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
...tagFilters
|
...tagFilters
|
||||||
];
|
];
|
||||||
|
|
||||||
|
this.worldMapChartFormat =
|
||||||
|
this.hasImpersonationId || this.user.settings.isRestrictedView
|
||||||
|
? `{0}%`
|
||||||
|
: `{0} ${this.user?.settings?.baseCurrency}`;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -257,8 +257,8 @@
|
|||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-world-map-chart
|
<gf-world-map-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[countries]="countries"
|
[countries]="countries"
|
||||||
|
[format]="worldMapChartFormat"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
></gf-world-map-chart>
|
></gf-world-map-chart>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -72,6 +72,7 @@ export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_OPTIONS: JobOptions = {
|
|||||||
export const MAX_CHART_ITEMS = 365;
|
export const MAX_CHART_ITEMS = 365;
|
||||||
|
|
||||||
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
|
export const PROPERTY_BENCHMARKS = 'BENCHMARKS';
|
||||||
|
export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS';
|
||||||
export const PROPERTY_COUPONS = 'COUPONS';
|
export const PROPERTY_COUPONS = 'COUPONS';
|
||||||
export const PROPERTY_CURRENCIES = 'CURRENCIES';
|
export const PROPERTY_CURRENCIES = 'CURRENCIES';
|
||||||
export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE';
|
export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE';
|
||||||
|
@ -6,12 +6,12 @@ import { Subscription } from './subscription.interface';
|
|||||||
export interface InfoItem {
|
export interface InfoItem {
|
||||||
baseCurrency: string;
|
baseCurrency: string;
|
||||||
benchmarks: Partial<SymbolProfile>[];
|
benchmarks: Partial<SymbolProfile>[];
|
||||||
|
countriesOfSubscribers?: string[];
|
||||||
currencies: string[];
|
currencies: string[];
|
||||||
demoAuthToken: string;
|
demoAuthToken: string;
|
||||||
fearAndGreedDataSource?: string;
|
fearAndGreedDataSource?: string;
|
||||||
globalPermissions: string[];
|
globalPermissions: string[];
|
||||||
isReadOnlyMode?: boolean;
|
isReadOnlyMode?: boolean;
|
||||||
lastDataGathering?: Date;
|
|
||||||
platforms: { id: string; name: string }[];
|
platforms: { id: string; name: string }[];
|
||||||
statistics: Statistics;
|
statistics: Statistics;
|
||||||
stripePublicKey?: string;
|
stripePublicKey?: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user