From 90a7a84ac5a9502ff3c002e31f16bbbe40bebb26 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 10 Jan 2023 20:52:05 +0100 Subject: [PATCH] Feature/add global heat map to landing page (#1584) * Add global heat map * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/app/info/info.service.ts | 5 +++++ .../world-map-chart.component.ts | 6 +++--- .../pages/landing/landing-page.component.ts | 18 +++++++++++++++++- .../src/app/pages/landing/landing-page.html | 15 +++++++++++++++ .../app/pages/landing/landing-page.module.ts | 4 ++++ .../src/app/pages/landing/landing-page.scss | 4 ++++ .../allocations/allocations-page.component.ts | 6 ++++++ .../allocations/allocations-page.html | 2 +- libs/common/src/lib/config.ts | 1 + .../src/lib/interfaces/info-item.interface.ts | 2 +- 11 files changed, 61 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a35e673c..63dded11b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Extended the landing page by a global heat map of subscribers + ### Changed - Improved the form of the import dividends dialog (disable while loading) diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 810611375..911243057 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/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 { DEMO_USER_ID, + PROPERTY_COUNTRIES_OF_SUBSCRIBERS, PROPERTY_IS_READ_ONLY_MODE, PROPERTY_SLACK_COMMUNITY_USERS, PROPERTY_STRIPE_CONFIG, @@ -92,6 +93,10 @@ export class InfoService { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { globalPermissions.push(permissions.enableSubscription); + info.countriesOfSubscribers = + ((await this.propertyService.getByKey( + PROPERTY_COUNTRIES_OF_SUBSCRIBERS + )) as string[]) ?? []; info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY'); } diff --git a/apps/client/src/app/components/world-map-chart/world-map-chart.component.ts b/apps/client/src/app/components/world-map-chart/world-map-chart.component.ts index 22e4adc66..ef49eebde 100644 --- a/apps/client/src/app/components/world-map-chart/world-map-chart.component.ts +++ b/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'] }) 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; public isLoading = true; @@ -71,7 +71,7 @@ export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit { applyData: 'value', data: { value: { - format: this.isInPercent ? `{0}%` : `{0} ${this.baseCurrency}` + format: this.format } }, values: this.countries diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index f8c2c6a23..f4a80c652 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -13,10 +13,14 @@ import { Subject } from 'rxjs'; templateUrl: './landing-page.html' }) export class LandingPageComponent implements OnDestroy, OnInit { + public countriesOfSubscribersMap: { + [code: string]: { value: number }; + } = {}; public currentYear = format(new Date(), 'yyyy'); public demoAuthToken: string; public deviceType: string; public hasPermissionForStatistics: boolean; + public hasPermissionForSubscription: boolean; public hasPermissionToCreateUser: boolean; public statistics: Statistics; public testimonials = [ @@ -48,13 +52,25 @@ export class LandingPageComponent implements OnDestroy, OnInit { private dataService: DataService, 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( globalPermissions, permissions.enableStatistics ); + this.hasPermissionForSubscription = hasPermission( + globalPermissions, + permissions.enableSubscription + ); + this.hasPermissionToCreateUser = hasPermission( globalPermissions, permissions.createUserAccount diff --git a/apps/client/src/app/pages/landing/landing-page.html b/apps/client/src/app/pages/landing/landing-page.html index 0c8f20bce..64f943f3c 100644 --- a/apps/client/src/app/pages/landing/landing-page.html +++ b/apps/client/src/app/pages/landing/landing-page.html @@ -269,6 +269,21 @@ +
+
+

+ Members from around the globe are using + Ghostfolio Premium +

+
+
+ +
+
+

diff --git a/apps/client/src/app/pages/landing/landing-page.module.ts b/apps/client/src/app/pages/landing/landing-page.module.ts index 37c67f270..4a3ce00cb 100644 --- a/apps/client/src/app/pages/landing/landing-page.module.ts +++ b/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 { MatCardModule } from '@angular/material/card'; 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 { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator'; import { GfValueModule } from '@ghostfolio/ui/value'; import { LandingPageRoutingModule } from './landing-page-routing.module'; @@ -14,7 +16,9 @@ import { LandingPageComponent } from './landing-page.component'; imports: [ CommonModule, GfLogoModule, + GfPremiumIndicatorModule, GfValueModule, + GfWorldMapChartModule, LandingPageRoutingModule, MatButtonModule, MatCardModule, diff --git a/apps/client/src/app/pages/landing/landing-page.scss b/apps/client/src/app/pages/landing/landing-page.scss index 74c416b08..bbc3c587a 100644 --- a/apps/client/src/app/pages/landing/landing-page.scss +++ b/apps/client/src/app/pages/landing/landing-page.scss @@ -9,6 +9,10 @@ } } + .customer-map-container { + aspect-ratio: 16 / 9; + } + .downloads { img { height: 2.5rem; diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index ea0f65ac0..16cad4a90 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/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 worldMapChartFormat: string; private unsubscribeSubject = new Subject(); @@ -193,6 +194,11 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { ...tagFilters ]; + this.worldMapChartFormat = + this.hasImpersonationId || this.user.settings.isRestrictedView + ? `{0}%` + : `{0} ${this.user?.settings?.baseCurrency}`; + this.changeDetectorRef.markForCheck(); } }); diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html index 43d838808..907b62d51 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html @@ -257,8 +257,8 @@
diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 5c8529231..91e83e2e0 100644 --- a/libs/common/src/lib/config.ts +++ b/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 PROPERTY_BENCHMARKS = 'BENCHMARKS'; +export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS'; export const PROPERTY_COUPONS = 'COUPONS'; export const PROPERTY_CURRENCIES = 'CURRENCIES'; export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE'; diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index 3751c6405..bcb28402c 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -6,12 +6,12 @@ import { Subscription } from './subscription.interface'; export interface InfoItem { baseCurrency: string; benchmarks: Partial[]; + countriesOfSubscribers?: string[]; currencies: string[]; demoAuthToken: string; fearAndGreedDataSource?: string; globalPermissions: string[]; isReadOnlyMode?: boolean; - lastDataGathering?: Date; platforms: { id: string; name: string }[]; statistics: Statistics; stripePublicKey?: string;