Browse Source

Feature/add global heat map to landing page (#1584)

* Add global heat map

* Update changelog
pull/1588/head
Thomas Kaul 2 years ago
committed by GitHub
parent
commit
90a7a84ac5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 5
      apps/api/src/app/info/info.service.ts
  3. 6
      apps/client/src/app/components/world-map-chart/world-map-chart.component.ts
  4. 18
      apps/client/src/app/pages/landing/landing-page.component.ts
  5. 15
      apps/client/src/app/pages/landing/landing-page.html
  6. 4
      apps/client/src/app/pages/landing/landing-page.module.ts
  7. 4
      apps/client/src/app/pages/landing/landing-page.scss
  8. 6
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  9. 2
      apps/client/src/app/pages/portfolio/allocations/allocations-page.html
  10. 1
      libs/common/src/lib/config.ts
  11. 2
      libs/common/src/lib/interfaces/info-item.interface.ts

4
CHANGELOG.md

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

5
apps/api/src/app/info/info.service.ts

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

6
apps/client/src/app/components/world-map-chart/world-map-chart.component.ts

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

18
apps/client/src/app/pages/landing/landing-page.component.ts

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

15
apps/client/src/app/pages/landing/landing-page.html

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

4
apps/client/src/app/pages/landing/landing-page.module.ts

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

4
apps/client/src/app/pages/landing/landing-page.scss

@ -9,6 +9,10 @@
} }
} }
.customer-map-container {
aspect-ratio: 16 / 9;
}
.downloads { .downloads {
img { img {
height: 2.5rem; height: 2.5rem;

6
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

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

2
apps/client/src/app/pages/portfolio/allocations/allocations-page.html

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

1
libs/common/src/lib/config.ts

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

2
libs/common/src/lib/interfaces/info-item.interface.ts

@ -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…
Cancel
Save