Feature/add interstitial for subscription (#1637)
* Add interstitial * Improve pricing page * Update changelog
This commit is contained in:
parent
662231e830
commit
5d8a50a80d
@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added an interstitial for the subscription
|
||||||
- Added a quote to the blog post _Ghostfolio auf Sackgeld.com vorgestellt_
|
- Added a quote to the blog post _Ghostfolio auf Sackgeld.com vorgestellt_
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Improved the pricing page
|
||||||
- Upgraded `Node.js` from version `16` to `18` (`Dockerfile`)
|
- Upgraded `Node.js` from version `16` to `18` (`Dockerfile`)
|
||||||
- Upgraded `prisma` from version `4.8.0` to `4.9.0`
|
- Upgraded `prisma` from version `4.8.0` to `4.9.0`
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ export class UserService {
|
|||||||
const {
|
const {
|
||||||
accessToken,
|
accessToken,
|
||||||
Account,
|
Account,
|
||||||
|
Analytics,
|
||||||
authChallenge,
|
authChallenge,
|
||||||
createdAt,
|
createdAt,
|
||||||
id,
|
id,
|
||||||
@ -107,7 +108,12 @@ export class UserService {
|
|||||||
thirdPartyId,
|
thirdPartyId,
|
||||||
updatedAt
|
updatedAt
|
||||||
} = await this.prismaService.user.findUnique({
|
} = await this.prismaService.user.findUnique({
|
||||||
include: { Account: true, Settings: true, Subscription: true },
|
include: {
|
||||||
|
Account: true,
|
||||||
|
Analytics: true,
|
||||||
|
Settings: true,
|
||||||
|
Subscription: true
|
||||||
|
},
|
||||||
where: userWhereUniqueInput
|
where: userWhereUniqueInput
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,7 +127,8 @@ export class UserService {
|
|||||||
role,
|
role,
|
||||||
Settings,
|
Settings,
|
||||||
thirdPartyId,
|
thirdPartyId,
|
||||||
updatedAt
|
updatedAt,
|
||||||
|
activityCount: Analytics?.activityCount
|
||||||
};
|
};
|
||||||
|
|
||||||
if (user?.Settings) {
|
if (user?.Settings) {
|
||||||
@ -154,15 +161,22 @@ export class UserService {
|
|||||||
(user.Settings.settings as UserSettings).viewMode = 'DEFAULT';
|
(user.Settings.settings as UserSettings).viewMode = 'DEFAULT';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentPermissions = getPermissions(user.role);
|
||||||
|
|
||||||
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
||||||
user.subscription =
|
user.subscription =
|
||||||
this.subscriptionService.getSubscription(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') {
|
if (user.subscription?.type === 'Premium') {
|
||||||
currentPermissions.push(permissions.reportDataGlitch);
|
currentPermissions.push(permissions.reportDataGlitch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.configurationService.get('ENABLE_FEATURE_READ_ONLY_MODE')) {
|
if (this.configurationService.get('ENABLE_FEATURE_READ_ONLY_MODE')) {
|
||||||
|
@ -25,6 +25,7 @@ import { DateFormats } from './adapter/date-formats';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { GfHeaderModule } from './components/header/header.module';
|
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 { authInterceptorProviders } from './core/auth.interceptor';
|
||||||
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
|
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
|
||||||
import { LanguageService } from './core/language.service';
|
import { LanguageService } from './core/language.service';
|
||||||
@ -40,6 +41,7 @@ export function NgxStripeFactory(): string {
|
|||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
GfHeaderModule,
|
GfHeaderModule,
|
||||||
|
GfSubscriptionInterstitialDialogModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MarkdownModule.forRoot(),
|
MarkdownModule.forRoot(),
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
|
@ -36,7 +36,7 @@ export class MarketDataDetailDialog implements OnDestroy {
|
|||||||
this.dateAdapter.setLocale(this.locale);
|
this.dateAdapter.setLocale(this.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCancel(): void {
|
public onCancel() {
|
||||||
this.dialogRef.close({ withRefresh: false });
|
this.dialogRef.close({ withRefresh: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export interface SubscriptionInterstitialDialogParams {}
|
@ -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<SubscriptionInterstitialDialog>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public onCancel() {
|
||||||
|
this.dialogRef.close({});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<h1 class="align-items-center d-flex" mat-dialog-title>
|
||||||
|
<span>Ghostfolio Premium</span>
|
||||||
|
<gf-premium-indicator class="ml-1"></gf-premium-indicator>
|
||||||
|
</h1>
|
||||||
|
<div class="flex-grow-1" mat-dialog-content>
|
||||||
|
<p class="h5" i18n>
|
||||||
|
Are you an ambitious investor who needs the full picture?
|
||||||
|
</p>
|
||||||
|
<p i18n>
|
||||||
|
By upgrading to Ghostfolio Premium, you will get these additional features:
|
||||||
|
</p>
|
||||||
|
<ul class="list-unstyled mb-3">
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
|
||||||
|
<span i18n>Portfolio Summary</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
|
||||||
|
<span i18n>Performance Benchmarks</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
|
||||||
|
<span i18n>Allocations</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
|
||||||
|
<span i18n>FIRE Calculator</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
|
||||||
|
<a i18n [routerLink]="['/features']">and more Features...</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>Refine your personal investment strategy now.</p>
|
||||||
|
</div>
|
||||||
|
<div class="justify-content-end" mat-dialog-actions>
|
||||||
|
<button i18n mat-button (click)="onCancel()">Skip</button>
|
||||||
|
<a color="primary" mat-flat-button [routerLink]="['/pricing']">
|
||||||
|
<span i18n>Upgrade Plan</span>
|
||||||
|
<ion-icon class="ml-1" name="arrow-forward-outline"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</div>
|
@ -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 {}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ export class CreateOrUpdateAccessDialog implements OnDestroy {
|
|||||||
|
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
|
|
||||||
public onCancel(): void {
|
public onCancel() {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
|
|||||||
this.platforms = platforms;
|
this.platforms = platforms;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCancel(): void {
|
public onCancel() {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ export class ImportActivitiesDialog implements OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onCancel(): void {
|
public onCancel() {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,50 +31,71 @@
|
|||||||
<mat-card class="d-flex flex-column h-100">
|
<mat-card class="d-flex flex-column h-100">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h4>Open Source</h4>
|
<h4>Open Source</h4>
|
||||||
<p>
|
<p i18n>
|
||||||
For tech-savvy investors who prefer to run
|
For tech-savvy investors who prefer to run Ghostfolio on their
|
||||||
<strong>Ghostfolio</strong> on their own infrastructure.
|
own infrastructure.
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-unstyled mb-3">
|
<ul class="list-unstyled mb-3">
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Unlimited Transactions</span>
|
<span i18n>Unlimited Transactions</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Portfolio Performance</span>
|
<span i18n>Unlimited Accounts</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Zen Mode</span>
|
<span i18n>Portfolio Performance</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Portfolio Summary</span>
|
<span i18n>Portfolio Summary</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Advanced Insights</span>
|
<span i18n>Performance Benchmarks</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>Allocations</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>FIRE Calculator</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<a i18n [routerLink]="['/features']">and more Features...</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p>Self-hosted, update manually.</p>
|
<p i18n>Self-hosted, update manually.</p>
|
||||||
<p class="h5 text-right">Free</p>
|
<p class="h5 text-right" i18n>Free</p>
|
||||||
<div
|
<div
|
||||||
*ngIf="user?.subscription?.type === 'Basic'"
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
class="d-none d-lg-block hidden mt-3 text-center"
|
class="d-none d-lg-block hidden mt-3 text-center"
|
||||||
@ -92,31 +113,54 @@
|
|||||||
[ngClass]="{ 'active': user?.subscription?.type === 'Basic' }"
|
[ngClass]="{ 'active': user?.subscription?.type === 'Basic' }"
|
||||||
>
|
>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h4 class="align-items-center d-flex">Basic</h4>
|
<div class="align-items-center d-flex mb-2">
|
||||||
<p>
|
<h4 class="flex-grow-1 m-0">Basic</h4>
|
||||||
|
<div *ngIf="user?.subscription?.type === 'Basic'">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-outline"></ion-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p i18n>
|
||||||
For new investors who are just getting started with trading.
|
For new investors who are just getting started with trading.
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-unstyled mb-3">
|
<ul class="list-unstyled mb-3">
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Unlimited Transactions</span>
|
<span i18n>Unlimited Transactions</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Portfolio Performance</span>
|
<span i18n>Unlimited Accounts</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>Portfolio Performance</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ion-icon
|
||||||
|
class="invisible"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ion-icon
|
||||||
|
class="invisible"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ion-icon
|
||||||
|
class="invisible"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Zen Mode</span>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
@ -132,8 +176,8 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p>Fully managed <strong>Ghostfolio</strong> cloud offering.</p>
|
<p i18n>Fully managed Ghostfolio cloud offering.</p>
|
||||||
<p class="h5 text-right">Free</p>
|
<p class="h5 text-right" i18n>Free</p>
|
||||||
<div
|
<div
|
||||||
*ngIf="user?.subscription?.type === 'Basic'"
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
class="d-none d-lg-block hidden mt-3 text-center"
|
class="d-none d-lg-block hidden mt-3 text-center"
|
||||||
@ -151,56 +195,82 @@
|
|||||||
[ngClass]="{ 'active': user?.subscription?.type === 'Premium' }"
|
[ngClass]="{ 'active': user?.subscription?.type === 'Premium' }"
|
||||||
>
|
>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h4 class="align-items-center d-flex">
|
<div class="align-items-center d-flex mb-2">
|
||||||
<span>Premium</span>
|
<h4 class="align-items-center d-flex flex-grow-1 m-0">
|
||||||
<gf-premium-indicator
|
<span>Premium</span>
|
||||||
class="ml-1"
|
<gf-premium-indicator
|
||||||
[enableLink]="false"
|
class="ml-1"
|
||||||
></gf-premium-indicator>
|
[enableLink]="false"
|
||||||
</h4>
|
></gf-premium-indicator>
|
||||||
<p>
|
</h4>
|
||||||
|
<div *ngIf="user?.subscription?.type === 'Premium'">
|
||||||
|
<ion-icon class="mr-1" name="checkmark-outline"></ion-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p i18n>
|
||||||
For ambitious investors who need the full picture of their
|
For ambitious investors who need the full picture of their
|
||||||
financial assets.
|
financial assets.
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-unstyled mb-3">
|
<ul class="list-unstyled mb-3">
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Unlimited Transactions</span>
|
<span i18n>Unlimited Transactions</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Portfolio Performance</span>
|
<span i18n>Unlimited Accounts</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Zen Mode</span>
|
<span i18n>Portfolio Performance</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Portfolio Summary</span>
|
<span i18n>Portfolio Summary</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<li class="align-items-center d-flex mb-1">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="mr-1"
|
||||||
name="checkmark-circle-outline"
|
name="checkmark-circle-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Advanced Insights</span>
|
<span i18n>Performance Benchmarks</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>Allocations</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span i18n>FIRE Calculator</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<a i18n [routerLink]="['/features']">and more Features...</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p>Fully managed <strong>Ghostfolio</strong> cloud offering.</p>
|
<p i18n>Fully managed Ghostfolio cloud offering.</p>
|
||||||
<p class="h5 text-right" [hidden]="!price">
|
<p class="h5 text-right" [hidden]="!price">
|
||||||
<span class="font-weight-normal">
|
<span class="font-weight-normal">
|
||||||
<ng-container *ngIf="coupon"
|
<ng-container *ngIf="coupon"
|
||||||
@ -221,11 +291,16 @@
|
|||||||
*ngIf="user?.subscription?.type === 'Basic'"
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
class="mt-3 text-center"
|
class="mt-3 text-center"
|
||||||
>
|
>
|
||||||
<a color="primary" mat-flat-button [routerLink]="['/account']">
|
<a
|
||||||
|
color="primary"
|
||||||
|
i18n
|
||||||
|
mat-flat-button
|
||||||
|
[routerLink]="['/account']"
|
||||||
|
>
|
||||||
Upgrade Plan
|
Upgrade Plan
|
||||||
</a>
|
</a>
|
||||||
<p class="m-0 text-muted">
|
<p class="m-0 text-muted">
|
||||||
<small>One-time payment, no auto-renewal.</small>
|
<small i18n>One-time payment, no auto-renewal.</small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
@ -235,10 +310,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngIf="!user" class="row">
|
<div *ngIf="!user" class="row">
|
||||||
<div class="col mt-3 text-center">
|
<div class="col mt-3 text-center">
|
||||||
<a color="primary" mat-flat-button [routerLink]="['/register']">
|
<a color="primary" i18n mat-flat-button [routerLink]="['/register']">
|
||||||
Get Started
|
Get Started
|
||||||
</a>
|
</a>
|
||||||
<p class="m-0 text-muted"><small>It’s free.</small></p>
|
<p class="m-0 text-muted"><small i18n>It’s free.</small></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
a {
|
p {
|
||||||
color: rgba(var(--palette-primary-500), 1);
|
a {
|
||||||
font-weight: 500;
|
color: rgba(var(--palette-primary-500), 1);
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: rgba(var(--palette-primary-300), 1);
|
color: rgba(var(--palette-primary-300), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,6 +19,10 @@
|
|||||||
border-color: rgba(var(--palette-primary-500), 1);
|
border-color: rgba(var(--palette-primary-500), 1);
|
||||||
box-shadow: 0 0 0 1px 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ObservableStore } from '@codewithdan/observable-store';
|
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 { 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 { throwError } from 'rxjs';
|
||||||
import { catchError, map } from 'rxjs/operators';
|
import { catchError, map, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { UserStoreActions } from './user-store.actions';
|
import { UserStoreActions } from './user-store.actions';
|
||||||
import { UserStoreState } from './user-store.state';
|
import { UserStoreState } from './user-store.state';
|
||||||
@ -13,10 +18,19 @@ import { UserStoreState } from './user-store.state';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class UserService extends ObservableStore<UserStoreState> {
|
export class UserService extends ObservableStore<UserStoreState> {
|
||||||
public constructor(private http: HttpClient) {
|
private deviceType: string;
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private deviceService: DeviceDetectorService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private http: HttpClient
|
||||||
|
) {
|
||||||
super({ trackStateHistory: true });
|
super({ trackStateHistory: true });
|
||||||
|
|
||||||
this.setState({ user: undefined }, UserStoreActions.Initialize);
|
this.setState({ user: undefined }, UserStoreActions.Initialize);
|
||||||
|
|
||||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(force = false) {
|
public get(force = false) {
|
||||||
@ -39,6 +53,26 @@ export class UserService extends ObservableStore<UserStoreState> {
|
|||||||
return this.http.get<User>('/api/v1/user').pipe(
|
return this.http.get<User>('/api/v1/user').pipe(
|
||||||
map((user) => {
|
map((user) => {
|
||||||
this.setState({ user }, UserStoreActions.GetUser);
|
this.setState({ user }, UserStoreActions.GetUser);
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasPermission(
|
||||||
|
user.permissions,
|
||||||
|
permissions.enableSubscriptionInterstitial
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const dialogRef = this.dialog.open(SubscriptionInterstitialDialog, {
|
||||||
|
autoFocus: false,
|
||||||
|
data: <SubscriptionInterstitialDialogParams>{},
|
||||||
|
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
||||||
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}),
|
}),
|
||||||
catchError(this.handleError)
|
catchError(this.handleError)
|
||||||
|
@ -5,6 +5,7 @@ import { UserSettings } from './user-settings.interface';
|
|||||||
|
|
||||||
export type UserWithSettings = User & {
|
export type UserWithSettings = User & {
|
||||||
Account: Account[];
|
Account: Account[];
|
||||||
|
activityCount: number;
|
||||||
permissions?: string[];
|
permissions?: string[];
|
||||||
Settings: Settings & { settings: UserSettings };
|
Settings: Settings & { settings: UserSettings };
|
||||||
subscription?: {
|
subscription?: {
|
||||||
|
@ -19,6 +19,7 @@ export const permissions = {
|
|||||||
enableSocialLogin: 'enableSocialLogin',
|
enableSocialLogin: 'enableSocialLogin',
|
||||||
enableStatistics: 'enableStatistics',
|
enableStatistics: 'enableStatistics',
|
||||||
enableSubscription: 'enableSubscription',
|
enableSubscription: 'enableSubscription',
|
||||||
|
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',
|
||||||
enableSystemMessage: 'enableSystemMessage',
|
enableSystemMessage: 'enableSystemMessage',
|
||||||
reportDataGlitch: 'reportDataGlitch',
|
reportDataGlitch: 'reportDataGlitch',
|
||||||
toggleReadOnlyMode: 'toggleReadOnlyMode',
|
toggleReadOnlyMode: 'toggleReadOnlyMode',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user