From efba7429c1eb00273ccd473a3f4ac74988a9ea76 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 25 Jan 2023 12:00:52 +0100 Subject: [PATCH 01/17] Bugfix/fix click of unknown accounts (#1629) * Check for unknown key * Update changelog --- CHANGELOG.md | 4 ++++ .../pages/portfolio/allocations/allocations-page.component.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5baf9159a..3f57dc3de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgraded `Node.js` from version `16` to `18` (`Dockerfile`) +### Fixed + +- Fixed the click of unknown accounts in the portfolio proportion chart component + ## 1.229.0 - 2023-01-21 ### Added 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 910629a3e..0dd32bf68 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 @@ -357,7 +357,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { } public onAccountChartClicked({ symbol }: UniqueAsset) { - if (symbol) { + if (symbol && symbol !== UNKNOWN_KEY) { this.router.navigate([], { queryParams: { accountId: symbol, accountDetailDialog: true } }); From 4d84459b5be0c1625c83b59de68829ba30ca58b7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 27 Jan 2023 08:49:31 +0100 Subject: [PATCH 02/17] Clean up imports (#1632) --- apps/api/src/app/import/import.controller.ts | 1 - apps/api/src/app/user/user.controller.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 4a9ef5093..2591ab638 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -20,7 +20,6 @@ import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { DataSource } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { isEmpty } from 'lodash'; import { ImportDataDto } from './import-data.dto'; import { ImportService } from './import.service'; diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index de79eaee9..6a1729b73 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -1,6 +1,4 @@ -import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; -import { PROPERTY_IS_USER_SIGNUP_ENABLED } from '@ghostfolio/common/config'; import { User, UserSettings } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -31,7 +29,6 @@ import { UserService } from './user.service'; @Controller('user') export class UserController { public constructor( - private readonly configurationService: ConfigurationService, private readonly jwtService: JwtService, private readonly propertyService: PropertyService, @Inject(REQUEST) private readonly request: RequestWithUser, From 662231e8300dec5d612653f2fb8794b080fd45e5 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 28 Jan 2023 09:41:33 +0100 Subject: [PATCH 03/17] Feature/upgrade prisma to version 4.9.0 (#1630) * Upgrade prisma to version 4.9.0 * Update changelog --- CHANGELOG.md | 1 + package.json | 4 ++-- yarn.lock | 36 ++++++++++++++++++------------------ 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f57dc3de..f68699fc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Upgraded `Node.js` from version `16` to `18` (`Dockerfile`) +- Upgraded `prisma` from version `4.8.0` to `4.9.0` ### Fixed diff --git a/package.json b/package.json index 9225074cc..44a897db6 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@nestjs/schedule": "2.1.0", "@nestjs/serve-static": "3.0.0", "@nrwl/angular": "15.0.13", - "@prisma/client": "4.8.0", + "@prisma/client": "4.9.0", "@simplewebauthn/browser": "5.2.1", "@simplewebauthn/server": "5.2.1", "@stripe/stripe-js": "1.22.0", @@ -119,7 +119,7 @@ "passport": "0.6.0", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.0", - "prisma": "4.8.0", + "prisma": "4.9.0", "reflect-metadata": "0.1.13", "rxjs": "7.5.6", "stripe": "8.199.0", diff --git a/yarn.lock b/yarn.lock index ac9b5acdf..6e61f8f3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3677,22 +3677,22 @@ dependencies: esquery "^1.0.1" -"@prisma/client@4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.8.0.tgz#6ec7adaca6a2e233d7e41dbe7cc6d0fa6143a407" - integrity sha512-Y1riB0p2W52kh3zgssP/YAhln3RjBFcJy3uwEiyjmU+TQYh6QTZDRFBo3JtBWuq2FyMOl1Rye8jxzUP+n0l5Cg== +"@prisma/client@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.9.0.tgz#4a4068f3540732ea5723c008d49ed684d20f9340" + integrity sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg== dependencies: - "@prisma/engines-version" "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe" + "@prisma/engines-version" "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5" -"@prisma/engines-version@4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe": - version "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe.tgz#30401aba1029e7d32e3cb717e705a7c92ccc211e" - integrity sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw== +"@prisma/engines-version@4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5": + version "4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5.tgz#9d817a5779fc05b107eb02f63d197ad296d60b3c" + integrity sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA== -"@prisma/engines@4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.8.0.tgz#5123c67dc0d2caa008268fc63081ca2d68b2ed7e" - integrity sha512-A1Asn2rxZMlLAj1HTyfaCv0VQrLUv034jVay05QlqZg1qiHPeA3/pGTfNMijbsMYCsGVxfWEJuaZZuNxXGMCrA== +"@prisma/engines@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.9.0.tgz#05a1411964e047c1bc43f777c7a1c69f86a2a26c" + integrity sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw== "@samverschueren/stream-to-observable@^0.3.0": version "0.3.1" @@ -16427,12 +16427,12 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz" integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A== -prisma@4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.8.0.tgz#634dbbdc9d3f76c61604880251673d08ccb6f02b" - integrity sha512-DWIhxvxt8f4h6MDd35mz7BJff+fu7HItW3WPDIEpCR3RzcOWyiHBbLQW5/DOgmf+pRLTjwXQob7kuTZVYUAw5w== +prisma@4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.9.0.tgz#295954b2a89cd35a0e6bcf66b2b036dbf80c75ee" + integrity sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w== dependencies: - "@prisma/engines" "4.8.0" + "@prisma/engines" "4.9.0" prismjs@^1.27.0, prismjs@^1.28.0: version "1.28.0" From 5d8a50a80dd9b54381365cf6b321b6606baaa58c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 28 Jan 2023 09:42:15 +0100 Subject: [PATCH 04/17] Feature/add interstitial for subscription (#1637) * Add interstitial * Improve pricing page * Update changelog --- CHANGELOG.md | 2 + apps/api/src/app/user/user.service.ts | 26 ++- apps/client/src/app/app.module.ts | 2 + .../market-data-detail-dialog.component.ts | 2 +- .../interfaces/interfaces.ts | 1 + ...scription-interstitial-dialog.component.ts | 22 +++ .../subscription-interstitial-dialog.html | 42 +++++ ...subscription-interstitial-dialog.module.ts | 21 +++ .../subscription-interstitial-dialog.scss | 11 ++ ...reate-or-update-access-dialog.component.ts | 2 +- ...eate-or-update-account-dialog.component.ts | 2 +- .../import-activities-dialog.component.ts | 2 +- .../src/app/pages/pricing/pricing-page.html | 171 +++++++++++++----- .../src/app/pages/pricing/pricing-page.scss | 16 +- .../src/app/services/user/user.service.ts | 40 +++- .../src/lib/interfaces/user-with-settings.ts | 1 + libs/common/src/lib/permissions.ts | 1 + 17 files changed, 298 insertions(+), 66 deletions(-) create mode 100644 apps/client/src/app/components/subscription-interstitial-dialog/interfaces/interfaces.ts create mode 100644 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts create mode 100644 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html create mode 100644 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.module.ts create mode 100644 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.scss diff --git a/CHANGELOG.md b/CHANGELOG.md index f68699fc8..788abe8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added an interstitial for the subscription - Added a quote to the blog post _Ghostfolio auf Sackgeld.com vorgestellt_ ### Changed +- Improved the pricing page - Upgraded `Node.js` from version `16` to `18` (`Dockerfile`) - Upgraded `prisma` from version `4.8.0` to `4.9.0` diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 13f82aa47..176de7004 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -97,6 +97,7 @@ export class UserService { const { accessToken, Account, + Analytics, authChallenge, createdAt, id, @@ -107,7 +108,12 @@ export class UserService { thirdPartyId, updatedAt } = await this.prismaService.user.findUnique({ - include: { Account: true, Settings: true, Subscription: true }, + include: { + Account: true, + Analytics: true, + Settings: true, + Subscription: true + }, where: userWhereUniqueInput }); @@ -121,7 +127,8 @@ export class UserService { role, Settings, thirdPartyId, - updatedAt + updatedAt, + activityCount: Analytics?.activityCount }; if (user?.Settings) { @@ -154,15 +161,22 @@ export class UserService { (user.Settings.settings as UserSettings).viewMode = 'DEFAULT'; } + let currentPermissions = getPermissions(user.role); + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { user.subscription = this.subscriptionService.getSubscription(Subscription); - } - let currentPermissions = getPermissions(user.role); + if ( + Analytics?.activityCount % 25 === 0 && + user.subscription?.type === 'Basic' + ) { + currentPermissions.push(permissions.enableSubscriptionInterstitial); + } - if (user.subscription?.type === 'Premium') { - currentPermissions.push(permissions.reportDataGlitch); + if (user.subscription?.type === 'Premium') { + currentPermissions.push(permissions.reportDataGlitch); + } } if (this.configurationService.get('ENABLE_FEATURE_READ_ONLY_MODE')) { diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts index 2d1f4cc5a..75055e019 100644 --- a/apps/client/src/app/app.module.ts +++ b/apps/client/src/app/app.module.ts @@ -25,6 +25,7 @@ import { DateFormats } from './adapter/date-formats'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { GfHeaderModule } from './components/header/header.module'; +import { GfSubscriptionInterstitialDialogModule } from './components/subscription-interstitial-dialog/subscription-interstitial-dialog.module'; import { authInterceptorProviders } from './core/auth.interceptor'; import { httpResponseInterceptorProviders } from './core/http-response.interceptor'; import { LanguageService } from './core/language.service'; @@ -40,6 +41,7 @@ export function NgxStripeFactory(): string { BrowserAnimationsModule, BrowserModule, GfHeaderModule, + GfSubscriptionInterstitialDialogModule, HttpClientModule, MarkdownModule.forRoot(), MatAutocompleteModule, diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts index 8c761ccdb..dddef0c8f 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts @@ -36,7 +36,7 @@ export class MarketDataDetailDialog implements OnDestroy { this.dateAdapter.setLocale(this.locale); } - public onCancel(): void { + public onCancel() { this.dialogRef.close({ withRefresh: false }); } diff --git a/apps/client/src/app/components/subscription-interstitial-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/subscription-interstitial-dialog/interfaces/interfaces.ts new file mode 100644 index 000000000..d93de3c4a --- /dev/null +++ b/apps/client/src/app/components/subscription-interstitial-dialog/interfaces/interfaces.ts @@ -0,0 +1 @@ +export interface SubscriptionInterstitialDialogParams {} diff --git a/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts new file mode 100644 index 000000000..0014df353 --- /dev/null +++ b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +import { SubscriptionInterstitialDialogParams } from './interfaces/interfaces'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'd-flex flex-column flex-grow-1 h-100' }, + selector: 'gf-subscription-interstitial-dialog', + styleUrls: ['./subscription-interstitial-dialog.scss'], + templateUrl: 'subscription-interstitial-dialog.html' +}) +export class SubscriptionInterstitialDialog { + public constructor( + @Inject(MAT_DIALOG_DATA) public data: SubscriptionInterstitialDialogParams, + public dialogRef: MatDialogRef + ) {} + + public onCancel() { + this.dialogRef.close({}); + } +} diff --git a/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html new file mode 100644 index 000000000..a45bb42a8 --- /dev/null +++ b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -0,0 +1,42 @@ +

+ Ghostfolio Premium + +

+
+

+ Are you an ambitious investor who needs the full picture? +

+

+ By upgrading to Ghostfolio Premium, you will get these additional features: +

+ +

Refine your personal investment strategy now.

+
+
+ + + Upgrade Plan + + +
diff --git a/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.module.ts b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.module.ts new file mode 100644 index 000000000..d7a7cfcf2 --- /dev/null +++ b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.module.ts @@ -0,0 +1,21 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { RouterModule } from '@angular/router'; +import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator'; + +import { SubscriptionInterstitialDialog } from './subscription-interstitial-dialog.component'; + +@NgModule({ + declarations: [SubscriptionInterstitialDialog], + imports: [ + CommonModule, + GfPremiumIndicatorModule, + MatButtonModule, + MatDialogModule, + RouterModule + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class GfSubscriptionInterstitialDialogModule {} diff --git a/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.scss b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.scss new file mode 100644 index 000000000..cc0da2076 --- /dev/null +++ b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.scss @@ -0,0 +1,11 @@ +:host { + display: block; + + .mat-dialog-content { + max-height: unset; + + ion-icon[name='checkmark-circle-outline'] { + color: rgba(var(--palette-accent-500), 1); + } + } +} diff --git a/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 1235caab6..1727191e8 100644 --- a/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -26,7 +26,7 @@ export class CreateOrUpdateAccessDialog implements OnDestroy { ngOnInit() {} - public onCancel(): void { + public onCancel() { this.dialogRef.close(); } diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index 23904e628..63641644a 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -36,7 +36,7 @@ export class CreateOrUpdateAccountDialog implements OnDestroy { this.platforms = platforms; } - public onCancel(): void { + public onCancel() { this.dialogRef.close(); } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index e72b7cde3..bf880632e 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -80,7 +80,7 @@ export class ImportActivitiesDialog implements OnDestroy { } } - public onCancel(): void { + public onCancel() { this.dialogRef.close(); } diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index 9245a6ac1..42cd5055b 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -31,50 +31,71 @@

Open Source

-

- For tech-savvy investors who prefer to run - Ghostfolio on their own infrastructure. +

+ For tech-savvy investors who prefer to run Ghostfolio on their + own infrastructure.

  • - Unlimited Transactions + Unlimited Transactions
  • - Portfolio Performance + Unlimited Accounts
  • - Zen Mode + Portfolio Performance
  • - Portfolio Summary + Portfolio Summary
  • - Advanced Insights + Performance Benchmarks +
  • +
  • + + Allocations +
  • +
  • + + FIRE Calculator +
  • +
  • + + and more Features...
-

Self-hosted, update manually.

-

Free

+

Self-hosted, update manually.

+

Free

- + Get Started -

It’s free.

+

It’s free.

diff --git a/apps/client/src/app/pages/pricing/pricing-page.scss b/apps/client/src/app/pages/pricing/pricing-page.scss index 8edf75b42..74b8facb8 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.scss +++ b/apps/client/src/app/pages/pricing/pricing-page.scss @@ -2,12 +2,14 @@ color: rgb(var(--dark-primary-text)); display: block; - a { - color: rgba(var(--palette-primary-500), 1); - font-weight: 500; + p { + a { + color: rgba(var(--palette-primary-500), 1); + font-weight: 500; - &:hover { - color: rgba(var(--palette-primary-300), 1); + &:hover { + color: rgba(var(--palette-primary-300), 1); + } } } @@ -17,6 +19,10 @@ border-color: rgba(var(--palette-primary-500), 1); box-shadow: 0 0 0 1px rgba(var(--palette-primary-500), 1); } + + ion-icon[name='checkmark-circle-outline'] { + color: rgba(var(--palette-accent-500), 1); + } } } diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index 79c15e085..c40d04880 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -1,10 +1,15 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { ObservableStore } from '@codewithdan/observable-store'; +import { SubscriptionInterstitialDialogParams } from '@ghostfolio/client/components/subscription-interstitial-dialog/interfaces/interfaces'; +import { SubscriptionInterstitialDialog } from '@ghostfolio/client/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component'; import { User } from '@ghostfolio/common/interfaces'; -import { of } from 'rxjs'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { DeviceDetectorService } from 'ngx-device-detector'; +import { of, Subject } from 'rxjs'; import { throwError } from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; +import { catchError, map, takeUntil } from 'rxjs/operators'; import { UserStoreActions } from './user-store.actions'; import { UserStoreState } from './user-store.state'; @@ -13,10 +18,19 @@ import { UserStoreState } from './user-store.state'; providedIn: 'root' }) export class UserService extends ObservableStore { - public constructor(private http: HttpClient) { + private deviceType: string; + private unsubscribeSubject = new Subject(); + + public constructor( + private deviceService: DeviceDetectorService, + private dialog: MatDialog, + private http: HttpClient + ) { super({ trackStateHistory: true }); this.setState({ user: undefined }, UserStoreActions.Initialize); + + this.deviceType = this.deviceService.getDeviceInfo().deviceType; } public get(force = false) { @@ -39,6 +53,26 @@ export class UserService extends ObservableStore { return this.http.get('/api/v1/user').pipe( map((user) => { this.setState({ user }, UserStoreActions.GetUser); + + if ( + hasPermission( + user.permissions, + permissions.enableSubscriptionInterstitial + ) + ) { + const dialogRef = this.dialog.open(SubscriptionInterstitialDialog, { + autoFocus: false, + data: {}, + height: this.deviceType === 'mobile' ? '97.5vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); + + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => {}); + } + return user; }), catchError(this.handleError) diff --git a/libs/common/src/lib/interfaces/user-with-settings.ts b/libs/common/src/lib/interfaces/user-with-settings.ts index 55324ff3d..80330d80c 100644 --- a/libs/common/src/lib/interfaces/user-with-settings.ts +++ b/libs/common/src/lib/interfaces/user-with-settings.ts @@ -5,6 +5,7 @@ import { UserSettings } from './user-settings.interface'; export type UserWithSettings = User & { Account: Account[]; + activityCount: number; permissions?: string[]; Settings: Settings & { settings: UserSettings }; subscription?: { diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index 6be7a3401..b9dc6806a 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -19,6 +19,7 @@ export const permissions = { enableSocialLogin: 'enableSocialLogin', enableStatistics: 'enableStatistics', enableSubscription: 'enableSubscription', + enableSubscriptionInterstitial: 'enableSubscriptionInterstitial', enableSystemMessage: 'enableSystemMessage', reportDataGlitch: 'reportDataGlitch', toggleReadOnlyMode: 'toggleReadOnlyMode', From 6aae0cc1e43dcb1db47e1ca5bc515c799d19066c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 29 Jan 2023 09:58:05 +0100 Subject: [PATCH 05/17] Bugfix/fix issue with value in value redaction interceptor (#1627) * Fix issue with value in value redaction interceptor * Fix format of world map * Update changelog --- CHANGELOG.md | 2 ++ .../src/app/portfolio/portfolio.controller.ts | 7 +++-- .../src/app/portfolio/portfolio.service.ts | 8 +++--- .../position-detail-dialog.component.ts | 2 +- .../allocations/allocations-page.component.ts | 5 +++- .../analysis/analysis-page.component.ts | 7 +++-- .../app/pages/public/public-page.component.ts | 5 +++- .../src/app/pages/public/public-page.html | 1 + apps/client/src/app/services/data.service.ts | 28 ++++++++++++++++--- .../historical-data-item.interface.ts | 2 ++ .../portfolio-position.interface.ts | 3 +- .../portfolio-public-details.interface.ts | 1 + 12 files changed, 53 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 788abe8aa..e102c5a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the unit format (`%`) in the global heat map component of the public page - Improved the pricing page - Upgraded `Node.js` from version `16` to `18` (`Dockerfile`) - Upgraded `prisma` from version `4.8.0` to `4.9.0` @@ -21,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the click of unknown accounts in the portfolio proportion chart component +- Fixed an issue with `value` in the value redaction interceptor for the impersonation mode ## 1.229.0 - 2023-01-21 diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index e607a5f96..795362195 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -131,7 +131,8 @@ export class PortfolioController { portfolioPosition.investment / totalInvestment; portfolioPosition.netPerformance = null; portfolioPosition.quantity = null; - portfolioPosition.value = portfolioPosition.value / totalValue; + portfolioPosition.valueInPercentage = + portfolioPosition.value / totalValue; } for (const [name, { current, original }] of Object.entries(accounts)) { @@ -322,7 +323,7 @@ export class PortfolioController { totalInvestment: new Big(totalInvestment) .div(performanceInformation.performance.totalInvestment) .toNumber(), - value: new Big(value) + valueInPercentage: new Big(value) .div(performanceInformation.performance.currentValue) .toNumber() }; @@ -437,7 +438,7 @@ export class PortfolioController { sectors: hasDetails ? portfolioPosition.sectors : [], symbol: portfolioPosition.symbol, url: portfolioPosition.url, - value: portfolioPosition.value / totalValue + valueInPercentage: portfolioPosition.value / totalValue }; } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 3410be220..bce6de674 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -785,8 +785,8 @@ export class PortfolioService { historicalDataArray.push({ averagePrice: orders[0].unitPrice, date: firstBuyDate, - quantity: orders[0].quantity, - value: orders[0].unitPrice + marketPrice: orders[0].unitPrice, + quantity: orders[0].quantity }); } @@ -815,9 +815,9 @@ export class PortfolioService { historicalDataArray.push({ date, + marketPrice, averagePrice: currentAveragePrice, - quantity: currentQuantity, - value: marketPrice + quantity: currentQuantity }); maxPrice = Math.max(marketPrice ?? 0, maxPrice); diff --git a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts index 870971cbc..fef9011d3 100644 --- a/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts +++ b/apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts @@ -111,7 +111,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit { return { date: historicalDataItem.date, - value: historicalDataItem.value + value: historicalDataItem.marketPrice }; } ); 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 0dd32bf68..674498c9d 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 @@ -21,6 +21,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Market } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { Account, AssetClass, DataSource } from '@prisma/client'; +import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; @@ -339,7 +340,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { dataSource: position.dataSource, name: position.name, symbol: prettifySymbol(symbol), - value: position.value + value: isNumber(position.value) + ? position.value + : position.valueInPercentage }; } diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index e91c13a11..2220f0eaa 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -19,7 +19,7 @@ import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { AssetClass, DataSource, SymbolProfile } from '@prisma/client'; import { differenceInDays } from 'date-fns'; -import { sortBy } from 'lodash'; +import { isNumber, sortBy } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; @@ -312,12 +312,13 @@ export class AnalysisPageComponent implements OnDestroy, OnInit { date, netPerformanceInPercentage, totalInvestment, - value + value, + valueInPercentage } of chart) { this.investments.push({ date, investment: totalInvestment }); this.performanceDataItems.push({ date, - value + value: isNumber(value) ? value : valueInPercentage }); this.performanceDataItemsInPercentage.push({ date, diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index fface3816..f25c69dae 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -9,6 +9,7 @@ import { } from '@ghostfolio/common/interfaces'; import { Market } from '@ghostfolio/common/types'; import { StatusCodes } from 'http-status-codes'; +import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; import { EMPTY, Subject } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; @@ -198,7 +199,9 @@ export class PublicPageComponent implements OnInit { this.symbols[prettifySymbol(symbol)] = { name: position.name, symbol: prettifySymbol(symbol), - value: position.value + value: isNumber(position.value) + ? position.value + : position.valueInPercentage }; } diff --git a/apps/client/src/app/pages/public/public-page.html b/apps/client/src/app/pages/public/public-page.html index 300b5e39e..dd332db83 100644 --- a/apps/client/src/app/pages/public/public-page.html +++ b/apps/client/src/app/pages/public/public-page.html @@ -77,6 +77,7 @@ diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 67a3e18cf..d4071caeb 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -41,7 +41,7 @@ import { AccountWithValue, DateRange, GroupBy } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; import { DataSource, Order as OrderModel } from '@prisma/client'; import { format, parseISO } from 'date-fns'; -import { cloneDeep, groupBy } from 'lodash'; +import { cloneDeep, groupBy, isNumber } from 'lodash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -299,6 +299,12 @@ export class DataService { ].dateOfFirstActivity ? parseISO(response.holdings[symbol].dateOfFirstActivity) : undefined; + + response.holdings[symbol].value = isNumber( + response.holdings[symbol].value + ) + ? response.holdings[symbol].value + : response.holdings[symbol].valueInPercentage; } } @@ -333,9 +339,23 @@ export class DataService { } public fetchPortfolioPublic(aId: string) { - return this.http.get( - `/api/v1/portfolio/public/${aId}` - ); + return this.http + .get(`/api/v1/portfolio/public/${aId}`) + .pipe( + map((response) => { + if (response.holdings) { + for (const symbol of Object.keys(response.holdings)) { + response.holdings[symbol].value = isNumber( + response.holdings[symbol].value + ) + ? response.holdings[symbol].value + : response.holdings[symbol].valueInPercentage; + } + } + + return response; + }) + ); } public fetchPortfolioReport() { diff --git a/libs/common/src/lib/interfaces/historical-data-item.interface.ts b/libs/common/src/lib/interfaces/historical-data-item.interface.ts index 59a53ee94..b8306ff86 100644 --- a/libs/common/src/lib/interfaces/historical-data-item.interface.ts +++ b/libs/common/src/lib/interfaces/historical-data-item.interface.ts @@ -2,9 +2,11 @@ export interface HistoricalDataItem { averagePrice?: number; date: string; grossPerformancePercent?: number; + marketPrice?: number; netPerformance?: number; netPerformanceInPercentage?: number; quantity?: number; totalInvestment?: number; value?: number; + valueInPercentage?: number; } diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index 126ffdab7..2b7c6c486 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -30,5 +30,6 @@ export interface PortfolioPosition { symbol: string; type?: string; url?: string; - value: number; + value?: number; + valueInPercentage?: number; } diff --git a/libs/common/src/lib/interfaces/portfolio-public-details.interface.ts b/libs/common/src/lib/interfaces/portfolio-public-details.interface.ts index 2ba22a3d2..7abc7f78f 100644 --- a/libs/common/src/lib/interfaces/portfolio-public-details.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-public-details.interface.ts @@ -18,6 +18,7 @@ export interface PortfolioPublicDetails { | 'symbol' | 'url' | 'value' + | 'valueInPercentage' >; }; } From e1806b4bd8650c3e16fb7a44b8f141eb588dbf06 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 29 Jan 2023 09:58:53 +0100 Subject: [PATCH 06/17] Feature/add sourceforge logo to landing page (#1638) * Add SourceForge * Update changelog --- CHANGELOG.md | 1 + .../src/app/pages/landing/landing-page.html | 8 + .../src/app/pages/landing/landing-page.scss | 5 + .../src/assets/images/logo-sourceforge.svg | 170 ++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 apps/client/src/assets/images/logo-sourceforge.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index e102c5a05..2b0ec38ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added an interstitial for the subscription +- Added _SourceForge_ to the _As seen in_ section on the landing page - Added a quote to the blog post _Ghostfolio auf Sackgeld.com vorgestellt_ ### Changed diff --git a/apps/client/src/app/pages/landing/landing-page.html b/apps/client/src/app/pages/landing/landing-page.html index 2b3024a45..8dd7cf002 100644 --- a/apps/client/src/app/pages/landing/landing-page.html +++ b/apps/client/src/app/pages/landing/landing-page.html @@ -154,6 +154,14 @@ title="Sackgeld.com – Apps für ein höheres Sackgeld" > +
+ +