Feature/move pricing section to page (#86)

* Add a dedicated pricing page

* Update changelog
pull/87/head
Thomas 3 years ago committed by GitHub
parent 42b9178d96
commit eb09d77251
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Harmonized the style of various tables
- Keep the color per type when switching between _Initial_ and _Current_ in pie charts
- Upgraded `chart.js` from version `3.0.2` to `3.2.1`
- Moved the pricing section to a dedicated page
- Improved the style of the transaction filtering component
### Fixed

@ -28,11 +28,6 @@ const routes: Routes = [
loadChildren: () =>
import('./pages/admin/admin-page.module').then((m) => m.AdminPageModule)
},
{
path: 'auth',
loadChildren: () =>
import('./pages/auth/auth-page.module').then((m) => m.AuthPageModule)
},
{
path: 'analysis',
loadChildren: () =>
@ -40,11 +35,23 @@ const routes: Routes = [
(m) => m.AnalysisPageModule
)
},
{
path: 'auth',
loadChildren: () =>
import('./pages/auth/auth-page.module').then((m) => m.AuthPageModule)
},
{
path: 'home',
loadChildren: () =>
import('./pages/home/home-page.module').then((m) => m.HomePageModule)
},
{
path: 'pricing',
loadChildren: () =>
import('./pages/pricing/pricing-page.module').then(
(m) => m.PricingPageModule
)
},
{
path: 'report',
loadChildren: () =>

@ -6,67 +6,76 @@
<span class="spacer"></span>
<a
class="d-none d-sm-block"
[routerLink]="['/']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('home') ? 'primary' : null"
[routerLink]="['/']"
>Overview</a
>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/analysis']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('analysis') ? 'primary' : null"
[routerLink]="['/analysis']"
>Analysis</a
>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/report']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('report') ? 'primary' : null"
[routerLink]="['/report']"
>X-ray</a
>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/transactions']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('transactions') ? 'primary' : null"
[routerLink]="['/transactions']"
>Transactions</a
>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/accounts']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('accounts') ? 'primary' : null"
[routerLink]="['/accounts']"
>Accounts</a
>
<a
*ngIf="hasPermissionToAccessAdminControl"
class="d-none d-sm-block mx-1"
[routerLink]="['/admin']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('admin') ? 'primary' : null"
[routerLink]="['/admin']"
>Admin Control</a
>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/resources']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('resources') ? 'primary' : null"
[routerLink]="['/resources']"
>Resources</a
>
<a
*ngIf="hasPermissionForSubscription"
class="d-none d-sm-block mx-1"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('pricing') ? 'primary' : null"
[routerLink]="['/pricing']"
>Pricing</a
>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/about']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
[routerLink]="['/about']"
>About</a
>
<button
@ -127,72 +136,81 @@
</ng-container>
<a
class="d-block d-sm-none"
[routerLink]="['/analysis']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('analysis') }"
[routerLink]="['/analysis']"
>Analysis</a
>
<a
class="d-block d-sm-none"
[routerLink]="['/report']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('report') }"
[routerLink]="['/report']"
>X-ray</a
>
<a
class="d-block d-sm-none"
[routerLink]="['/transactions']"
i18n
mat-menu-item
[ngClass]="{
'font-weight-bold': currentRoute?.startsWith('transactions')
}"
[routerLink]="['/transactions']"
>Transactions</a
>
<a
class="d-block d-sm-none"
[routerLink]="['/accounts']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('accounts') }"
[routerLink]="['/accounts']"
>Accounts</a
>
<a
class="align-items-center d-flex"
[routerLink]="['/account']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('account') }"
[routerLink]="['/account']"
>Ghostfolio Account</a
>
<a
*ngIf="hasPermissionToAccessAdminControl"
class="d-block d-sm-none"
[routerLink]="['/admin']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('admin') }"
[routerLink]="['/admin']"
>Admin Control</a
>
<hr class="m-0" />
<a
class="d-block d-sm-none"
[routerLink]="['/resources']"
i18n
mat-menu-item
[ngClass]="{
'font-weight-bold': currentRoute?.startsWith('resources')
}"
[routerLink]="['/resources']"
>Resources</a
>
<a
*ngIf="hasPermissionForSubscription"
class="d-block d-sm-none"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('pricing') }"
[routerLink]="['/pricing']"
>Pricing</a
>
<a
class="d-block d-sm-none"
[routerLink]="['/about']"
i18n
mat-menu-item
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('about') }"
[routerLink]="['/about']"
>About Ghostfolio</a
>
<hr class="d-block d-sm-none m-0" />
@ -202,19 +220,19 @@
<ng-container *ngIf="user === null">
<a
*ngIf="currentRoute && currentRoute !== 'start'"
[routerLink]="['/']"
class="mx-2 no-min-width px-2"
mat-button
[routerLink]="['/']"
>
<gf-logo></gf-logo>
</a>
<span class="spacer"></span>
<a
class="d-none d-sm-block mx-1"
[routerLink]="['/about']"
i18n
mat-flat-button
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
[routerLink]="['/about']"
>About</a
>
<a

@ -27,8 +27,9 @@ export class HeaderComponent implements OnChanges {
@Input() info: InfoItem;
@Input() user: User;
public hasPermissionToAccessAdminControl: boolean;
public hasPermissionForSocialLogin: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessAdminControl: boolean;
public impersonationId: string;
private unsubscribeSubject = new Subject<void>();
@ -49,16 +50,21 @@ export class HeaderComponent implements OnChanges {
public ngOnChanges() {
if (this.user) {
this.hasPermissionForSocialLogin = hasPermission(
this.info?.globalPermissions,
permissions.enableSocialLogin
);
this.hasPermissionForSubscription = hasPermission(
this.info?.globalPermissions,
permissions.enableSubscription
);
this.hasPermissionToAccessAdminControl = hasPermission(
this.user.permissions,
permissions.accessAdminControl
);
}
this.hasPermissionForSocialLogin = hasPermission(
this.info?.globalPermissions,
permissions.enableSocialLogin
);
}
public impersonateAccount(aId: string) {

@ -1,9 +1,8 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { InfoItem } from '@ghostfolio/api/app/info/interfaces/info-item.interface';
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
import { DataService } from '@ghostfolio/client/services/data.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { baseCurrency, hasPermission, permissions } from '@ghostfolio/helper';
import { baseCurrency } from '@ghostfolio/helper';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -16,7 +15,6 @@ import { environment } from '../../../environments/environment';
})
export class AboutPageComponent implements OnInit {
public baseCurrency = baseCurrency;
public hasPermissionForSubscription: boolean;
public isLoggedIn: boolean;
public lastPublish = environment.lastPublish;
public user: User;
@ -37,15 +35,6 @@ export class AboutPageComponent implements OnInit {
* Initializes the controller
*/
public ngOnInit() {
this.dataService.fetchInfo().subscribe((info) => {
this.hasPermissionForSubscription = hasPermission(
info.globalPermissions,
permissions.enableSubscription
);
this.cd.markForCheck();
});
this.isLoggedIn = !!this.tokenStorageService.getToken();
if (this.isLoggedIn)

@ -61,105 +61,6 @@
</div>
</div>
<div *ngIf="hasPermissionForSubscription" class="mb-5 row">
<div class="col">
<h3 class="mb-3 text-center" i18n>Pricing Plans</h3>
<div class="row">
<div class="col-xs-12 col-md-6">
<mat-card class="mb-3">
<h4 i18n>Open Source</h4>
<p>Host your <strong>Ghostfolio</strong> instance by yourself.</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Advanced Insights</span>
</li>
</ul>
<p class="h5 text-right">
<span>Free</span>
</p>
</mat-card>
</div>
<div class="col-xs-12 col-md-6">
<mat-card
class="mb-3"
[ngClass]="{ 'active': user?.subscription?.type === 'Trial' }"
>
<h4 class="align-items-center d-flex" i18n>
Diamond
<ion-icon
class="ml-1 text-muted"
name="diamond-outline"
></ion-icon>
</h4>
<p>Get a managed <strong>Ghostfolio</strong> cloud offering.</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Advanced Insights</span>
</li>
</ul>
<p class="h5 text-right">
<span class="font-weight-normal"
>{{ user?.settings.baseCurrency || baseCurrency }}
<strong>2.99</strong>
<del class="ml-1 text-muted">3.99</del> / Month</span
>
</p>
</mat-card>
</div>
</div>
</div>
</div>
<div class="mb-5 row">
<div class="col">
<h3 class="mb-3 text-center" i18n>Changelog</h3>

@ -7,10 +7,6 @@
}
.mat-card {
&.active {
border-color: rgba(var(--palette-primary-500), 1);
}
&.changelog {
::ng-deep {
markdown {

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PricingPageComponent } from './pricing-page.component';
const routes: Routes = [{ path: '', component: PricingPageComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PricingPageRoutingModule {}

@ -0,0 +1,53 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { baseCurrency } from '@ghostfolio/helper';
import { Subject } from 'rxjs';
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
import { takeUntil } from 'rxjs/operators';
import { DataService } from '@ghostfolio/client/services/data.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
@Component({
selector: 'gf-pricing-page',
templateUrl: './pricing-page.html',
styleUrls: ['./pricing-page.scss']
})
export class PricingPageComponent implements OnInit {
public baseCurrency = baseCurrency;
public isLoggedIn: boolean;
public user: User;
private unsubscribeSubject = new Subject<void>();
/**
* @constructor
*/
public constructor(
private cd: ChangeDetectorRef,
private dataService: DataService,
private tokenStorageService: TokenStorageService
) {}
/**
* Initializes the controller
*/
public ngOnInit() {
this.isLoggedIn = !!this.tokenStorageService.getToken();
if (this.isLoggedIn)
this.tokenStorageService
.onChangeHasToken()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.dataService.fetchUser().subscribe((user) => {
this.user = user;
this.cd.markForCheck();
});
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

@ -0,0 +1,102 @@
<div class="container">
<div class="row">
<div class="col">
<h3 class="d-flex justify-content-center mb-3" i18n>Pricing Plans</h3>
<div class="row">
<div class="col-xs-12 col-md-6">
<mat-card class="mb-3">
<h4 i18n>Open Source</h4>
<p>Host your <strong>Ghostfolio</strong> instance by yourself.</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Advanced Insights</span>
</li>
</ul>
<p class="h5 text-right">
<span>Free</span>
</p>
</mat-card>
</div>
<div class="col-xs-12 col-md-6">
<mat-card
class="mb-3"
[ngClass]="{ 'active': user?.subscription?.type === 'Trial' }"
>
<h4 class="align-items-center d-flex" i18n>
Diamond
<ion-icon
class="ml-1 text-muted"
name="diamond-outline"
></ion-icon>
</h4>
<p>
Get a fully managed <strong>Ghostfolio</strong> cloud offering.
</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
name="checkmark-circle-outline"
></ion-icon>
<span>Advanced Insights</span>
</li>
</ul>
<p class="h5 text-right">
<span class="font-weight-normal"
>{{ user?.settings.baseCurrency || baseCurrency }}
<strong>2.99</strong>
<del class="ml-1 text-muted">3.99</del> / Month</span
>
</p>
</mat-card>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { PricingPageRoutingModule } from './pricing-page-routing.module';
import { PricingPageComponent } from './pricing-page.component';
@NgModule({
declarations: [PricingPageComponent],
exports: [],
imports: [CommonModule, MatCardModule, PricingPageRoutingModule],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class PricingPageModule {}

@ -0,0 +1,14 @@
:host {
color: rgb(var(--dark-primary-text));
display: block;
.mat-card {
&.active {
border-color: rgba(var(--palette-primary-500), 1);
}
}
}
:host-context(.is-dark-theme) {
color: rgb(var(--light-primary-text));
}
Loading…
Cancel
Save