diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 780860232..a5a1c6d92 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -13,7 +13,6 @@ import { PROPERTY_DEMO_USER_ID, PROPERTY_IS_READ_ONLY_MODE, PROPERTY_SLACK_COMMUNITY_USERS, - PROPERTY_STRIPE_CONFIG, ghostfolioFearAndGreedIndexDataSource } from '@ghostfolio/common/config'; import { @@ -21,13 +20,8 @@ import { encodeDataSource, extractNumberFromString } from '@ghostfolio/common/helper'; -import { - InfoItem, - Statistics, - SubscriptionOffer -} from '@ghostfolio/common/interfaces'; +import { InfoItem, Statistics } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; -import { SubscriptionOfferKey } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @@ -100,8 +94,7 @@ export class InfoService { demoAuthToken, isUserSignupEnabled, platforms, - statistics, - subscriptionOffers + statistics ] = await Promise.all([ this.benchmarkService.getBenchmarkAssetProfiles(), this.getDemoAuthToken(), @@ -109,8 +102,7 @@ export class InfoService { this.platformService.getPlatforms({ orderBy: { name: 'asc' } }), - this.getStatistics(), - this.getSubscriptionOffers() + this.getStatistics() ]); if (isUserSignupEnabled) { @@ -125,7 +117,6 @@ export class InfoService { isReadOnlyMode, platforms, statistics, - subscriptionOffers, baseCurrency: DEFAULT_CURRENCY, currencies: this.exchangeRateDataService.getCurrencies() }; @@ -299,19 +290,6 @@ export class InfoService { return statistics; } - private async getSubscriptionOffers(): Promise<{ - [offer in SubscriptionOfferKey]: SubscriptionOffer; - }> { - if (!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { - return undefined; - } - - return ( - ((await this.propertyService.getByKey(PROPERTY_STRIPE_CONFIG)) as any) ?? - {} - ); - } - private async getUptime(): Promise { { try { diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index ae0260d8c..10f374397 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -158,26 +158,30 @@ export class SubscriptionService { } } - public getSubscription({ + public async getSubscription({ createdAt, subscriptions }: { createdAt: UserWithSettings['createdAt']; subscriptions: Subscription[]; - }): UserWithSettings['subscription'] { + }): Promise { if (subscriptions.length > 0) { const { expiresAt, price } = subscriptions.reduce((a, b) => { return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b; }); - let offer: SubscriptionOfferKey = price ? 'renewal' : 'default'; + let offerKey: SubscriptionOfferKey = price ? 'renewal' : 'default'; if (isBefore(createdAt, parseDate('2023-01-01'))) { - offer = 'renewal-early-bird-2023'; + offerKey = 'renewal-early-bird-2023'; } else if (isBefore(createdAt, parseDate('2024-01-01'))) { - offer = 'renewal-early-bird-2024'; + offerKey = 'renewal-early-bird-2024'; } + const offer = await this.getSubscriptionOffer({ + key: offerKey + }); + return { expiresAt, offer, @@ -186,10 +190,30 @@ export class SubscriptionService { : SubscriptionType.Basic }; } else { + const offer = await this.getSubscriptionOffer({ + key: 'default' + }); + return { - offer: 'default', + offer, type: SubscriptionType.Basic }; } } + + private async getSubscriptionOffer({ + key + }: { + key: SubscriptionOfferKey; + }): Promise { + if (!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + return undefined; + } + + const offers = + ((await this.propertyService.getByKey(PROPERTY_STRIPE_CONFIG)) as any) ?? + {}; + + return offers[key]; + } } diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index e9b8078b1..b48be28c4 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -339,7 +339,7 @@ export class UserService { } if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { - user.subscription = this.subscriptionService.getSubscription({ + user.subscription = await this.subscriptionService.getSubscription({ createdAt: user.createdAt, subscriptions: Subscription }); diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 4220208c0..38ed64cc9 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -142,10 +142,6 @@ export class AppComponent implements OnDestroy, OnInit { permissions.enableFearAndGreedIndex ); - this.hasPromotion = - !!this.info?.subscriptionOffers?.default?.coupon || - !!this.info?.subscriptionOffers?.default?.durationExtension; - this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntil(this.unsubscribeSubject)) @@ -242,12 +238,8 @@ export class AppComponent implements OnDestroy, OnInit { this.canCreateAccount || !!this.user?.systemMessage; this.hasPromotion = - !!this.info?.subscriptionOffers?.[ - this.user?.subscription?.offer ?? 'default' - ]?.coupon || - !!this.info?.subscriptionOffers?.[ - this.user?.subscription?.offer ?? 'default' - ]?.durationExtension; + !!this.user?.subscription?.offer?.coupon || + !!this.user?.subscription?.offer?.durationExtension; this.initializeTheme(this.user?.settings.colorScheme); diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts index 2214d91f9..8d54f737c 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts @@ -51,8 +51,7 @@ export class UserAccountMembershipComponent implements OnDestroy { private stripeService: StripeService, private userService: UserService ) { - const { baseCurrency, globalPermissions, subscriptionOffers } = - this.dataService.fetchInfo(); + const { baseCurrency, globalPermissions } = this.dataService.fetchInfo(); this.baseCurrency = baseCurrency; @@ -81,18 +80,12 @@ export class UserAccountMembershipComponent implements OnDestroy { permissions.updateUserSettings ); - this.coupon = - subscriptionOffers?.[this.user.subscription.offer]?.coupon; - this.couponId = - subscriptionOffers?.[this.user.subscription.offer]?.couponId; + this.coupon = this.user?.subscription?.offer?.coupon; + this.couponId = this.user?.subscription?.offer?.couponId; this.durationExtension = - subscriptionOffers?.[ - this.user.subscription.offer - ]?.durationExtension; - this.price = - subscriptionOffers?.[this.user.subscription.offer]?.price; - this.priceId = - subscriptionOffers?.[this.user.subscription.offer]?.priceId; + this.user?.subscription?.offer?.durationExtension; + this.price = this.user?.subscription?.offer?.price; + this.priceId = this.user?.subscription?.offer?.priceId; this.changeDetectorRef.markForCheck(); } diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 62e2bec0b..6cf79d873 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -55,14 +55,9 @@ export class PricingPageComponent implements OnDestroy, OnInit { ) {} public ngOnInit() { - const { baseCurrency, subscriptionOffers } = this.dataService.fetchInfo(); + const { baseCurrency } = this.dataService.fetchInfo(); this.baseCurrency = baseCurrency; - this.coupon = subscriptionOffers?.default?.coupon; - this.durationExtension = subscriptionOffers?.default?.durationExtension; - this.label = subscriptionOffers?.default?.label; - this.price = subscriptionOffers?.default?.price; - this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((state) => { @@ -74,20 +69,13 @@ export class PricingPageComponent implements OnDestroy, OnInit { permissions.updateUserSettings ); - this.coupon = - subscriptionOffers?.[this.user?.subscription?.offer]?.coupon; - this.couponId = - subscriptionOffers?.[this.user.subscription.offer]?.couponId; + this.coupon = this.user?.subscription?.offer?.coupon; + this.couponId = this.user?.subscription?.offer?.couponId; this.durationExtension = - subscriptionOffers?.[ - this.user?.subscription?.offer - ]?.durationExtension; - this.label = - subscriptionOffers?.[this.user?.subscription?.offer]?.label; - this.price = - subscriptionOffers?.[this.user?.subscription?.offer]?.price; - this.priceId = - subscriptionOffers?.[this.user.subscription.offer]?.priceId; + this.user?.subscription?.offer?.durationExtension; + this.label = this.user?.subscription?.offer?.label; + this.price = this.user?.subscription?.offer?.price; + this.priceId = this.user?.subscription?.offer?.priceId; this.changeDetectorRef.markForCheck(); } diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts index 6a8543e71..f19ac476b 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -1,11 +1,13 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { Product } from '@ghostfolio/common/interfaces'; +import { User } from '@ghostfolio/common/interfaces'; import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { translate } from '@ghostfolio/ui/i18n'; import { Component, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { ActivatedRoute, RouterModule } from '@angular/router'; +import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -26,17 +28,16 @@ export class GfProductPageComponent implements OnInit { 'personal-finance-tools' ]; public tags: string[]; + public user: User; + + private unsubscribeSubject = new Subject(); public constructor( - private dataService: DataService, - private route: ActivatedRoute + private route: ActivatedRoute, + private userService: UserService ) {} public ngOnInit() { - const { subscriptionOffers } = this.dataService.fetchInfo(); - - this.price = subscriptionOffers?.default?.price; - this.product1 = { founded: 2021, hasFreePlan: true, @@ -100,5 +101,20 @@ export class GfProductPageComponent implements OnInit { ].sort((a, b) => { return a.localeCompare(b, undefined, { sensitivity: 'base' }); }); + + this.userService.stateChanged + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((state) => { + if (state?.user) { + this.user = state.user; + + this.price = this.user?.subscription?.offer?.price; + } + }); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); } } diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index bd3eb1f94..b1e0eb33d 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,9 +1,6 @@ -import { SubscriptionOfferKey } from '@ghostfolio/common/types'; - import { Platform, SymbolProfile } from '@prisma/client'; import { Statistics } from './statistics.interface'; -import { SubscriptionOffer } from './subscription-offer.interface'; export interface InfoItem { baseCurrency: string; @@ -18,5 +15,4 @@ export interface InfoItem { platforms: Platform[]; statistics: Statistics; stripePublicKey?: string; - subscriptionOffers: { [offer in SubscriptionOfferKey]: SubscriptionOffer }; } diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index 84f48d1dc..a48317fad 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -1,8 +1,8 @@ -import { SubscriptionOfferKey } from '@ghostfolio/common/types'; import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; import { Access, Account, Tag } from '@prisma/client'; +import { SubscriptionOffer } from './subscription-offer.interface'; import { SystemMessage } from './system-message.interface'; import { UserSettings } from './user-settings.interface'; @@ -18,7 +18,7 @@ export interface User { systemMessage?: SystemMessage; subscription: { expiresAt?: Date; - offer: SubscriptionOfferKey; + offer: SubscriptionOffer; type: SubscriptionType; }; tags: (Tag & { isUsed: boolean })[]; diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index 668486a94..8ffd345db 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -17,6 +17,7 @@ import type { Market } from './market.type'; import type { OrderWithAccount } from './order-with-account.type'; import type { RequestWithUser } from './request-with-user.type'; import type { SubscriptionOfferKey } from './subscription-offer-key.type'; +import type { SubscriptionType } from './subscription-type.type'; import type { UserWithSettings } from './user-with-settings.type'; import type { ViewMode } from './view-mode.type'; @@ -40,6 +41,7 @@ export type { OrderWithAccount, RequestWithUser, SubscriptionOfferKey, + SubscriptionType, UserWithSettings, ViewMode }; diff --git a/libs/common/src/lib/types/user-with-settings.type.ts b/libs/common/src/lib/types/user-with-settings.type.ts index 5f9835176..5fb25a664 100644 --- a/libs/common/src/lib/types/user-with-settings.type.ts +++ b/libs/common/src/lib/types/user-with-settings.type.ts @@ -1,6 +1,5 @@ -import { UserSettings } from '@ghostfolio/common/interfaces'; -import { SubscriptionOfferKey } from '@ghostfolio/common/types'; -import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; +import { SubscriptionOffer, UserSettings } from '@ghostfolio/common/interfaces'; +import { SubscriptionType } from '@ghostfolio/common/types'; import { Access, Account, Settings, User } from '@prisma/client'; @@ -14,7 +13,7 @@ export type UserWithSettings = User & { Settings: Settings & { settings: UserSettings }; subscription?: { expiresAt?: Date; - offer: SubscriptionOfferKey; + offer: SubscriptionOffer; type: SubscriptionType; }; };