Feature/move pricing section to page (#86)
* Add a dedicated pricing page * Update changelog
This commit is contained in:
parent
42b9178d96
commit
eb09d77251
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Harmonized the style of various tables
|
- Harmonized the style of various tables
|
||||||
- Keep the color per type when switching between _Initial_ and _Current_ in pie charts
|
- 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`
|
- 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
|
- Improved the style of the transaction filtering component
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -28,11 +28,6 @@ const routes: Routes = [
|
|||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/admin/admin-page.module').then((m) => m.AdminPageModule)
|
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',
|
path: 'analysis',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
@ -40,11 +35,23 @@ const routes: Routes = [
|
|||||||
(m) => m.AnalysisPageModule
|
(m) => m.AnalysisPageModule
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'auth',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/auth/auth-page.module').then((m) => m.AuthPageModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: 'home',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./pages/home/home-page.module').then((m) => m.HomePageModule)
|
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',
|
path: 'report',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -6,67 +6,76 @@
|
|||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block"
|
class="d-none d-sm-block"
|
||||||
[routerLink]="['/']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('home') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('home') ? 'primary' : null"
|
||||||
|
[routerLink]="['/']"
|
||||||
>Overview</a
|
>Overview</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/analysis']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('analysis') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('analysis') ? 'primary' : null"
|
||||||
|
[routerLink]="['/analysis']"
|
||||||
>Analysis</a
|
>Analysis</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/report']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('report') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('report') ? 'primary' : null"
|
||||||
|
[routerLink]="['/report']"
|
||||||
>X-ray</a
|
>X-ray</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/transactions']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('transactions') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('transactions') ? 'primary' : null"
|
||||||
|
[routerLink]="['/transactions']"
|
||||||
>Transactions</a
|
>Transactions</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/accounts']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('accounts') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('accounts') ? 'primary' : null"
|
||||||
|
[routerLink]="['/accounts']"
|
||||||
>Accounts</a
|
>Accounts</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
*ngIf="hasPermissionToAccessAdminControl"
|
*ngIf="hasPermissionToAccessAdminControl"
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/admin']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('admin') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('admin') ? 'primary' : null"
|
||||||
|
[routerLink]="['/admin']"
|
||||||
>Admin Control</a
|
>Admin Control</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/resources']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('resources') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('resources') ? 'primary' : null"
|
||||||
|
[routerLink]="['/resources']"
|
||||||
>Resources</a
|
>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
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/about']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
|
||||||
|
[routerLink]="['/about']"
|
||||||
>About</a
|
>About</a
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@ -127,72 +136,81 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
<a
|
<a
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/analysis']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('analysis') }"
|
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('analysis') }"
|
||||||
|
[routerLink]="['/analysis']"
|
||||||
>Analysis</a
|
>Analysis</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/report']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('report') }"
|
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('report') }"
|
||||||
|
[routerLink]="['/report']"
|
||||||
>X-ray</a
|
>X-ray</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/transactions']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'font-weight-bold': currentRoute?.startsWith('transactions')
|
'font-weight-bold': currentRoute?.startsWith('transactions')
|
||||||
}"
|
}"
|
||||||
|
[routerLink]="['/transactions']"
|
||||||
>Transactions</a
|
>Transactions</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/accounts']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('accounts') }"
|
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('accounts') }"
|
||||||
|
[routerLink]="['/accounts']"
|
||||||
>Accounts</a
|
>Accounts</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="align-items-center d-flex"
|
class="align-items-center d-flex"
|
||||||
[routerLink]="['/account']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('account') }"
|
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('account') }"
|
||||||
|
[routerLink]="['/account']"
|
||||||
>Ghostfolio Account</a
|
>Ghostfolio Account</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
*ngIf="hasPermissionToAccessAdminControl"
|
*ngIf="hasPermissionToAccessAdminControl"
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/admin']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('admin') }"
|
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('admin') }"
|
||||||
|
[routerLink]="['/admin']"
|
||||||
>Admin Control</a
|
>Admin Control</a
|
||||||
>
|
>
|
||||||
<hr class="m-0" />
|
<hr class="m-0" />
|
||||||
<a
|
<a
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/resources']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'font-weight-bold': currentRoute?.startsWith('resources')
|
'font-weight-bold': currentRoute?.startsWith('resources')
|
||||||
}"
|
}"
|
||||||
|
[routerLink]="['/resources']"
|
||||||
>Resources</a
|
>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
|
<a
|
||||||
class="d-block d-sm-none"
|
class="d-block d-sm-none"
|
||||||
[routerLink]="['/about']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('about') }"
|
[ngClass]="{ 'font-weight-bold': currentRoute?.startsWith('about') }"
|
||||||
|
[routerLink]="['/about']"
|
||||||
>About Ghostfolio</a
|
>About Ghostfolio</a
|
||||||
>
|
>
|
||||||
<hr class="d-block d-sm-none m-0" />
|
<hr class="d-block d-sm-none m-0" />
|
||||||
@ -202,19 +220,19 @@
|
|||||||
<ng-container *ngIf="user === null">
|
<ng-container *ngIf="user === null">
|
||||||
<a
|
<a
|
||||||
*ngIf="currentRoute && currentRoute !== 'start'"
|
*ngIf="currentRoute && currentRoute !== 'start'"
|
||||||
[routerLink]="['/']"
|
|
||||||
class="mx-2 no-min-width px-2"
|
class="mx-2 no-min-width px-2"
|
||||||
mat-button
|
mat-button
|
||||||
|
[routerLink]="['/']"
|
||||||
>
|
>
|
||||||
<gf-logo></gf-logo>
|
<gf-logo></gf-logo>
|
||||||
</a>
|
</a>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<a
|
<a
|
||||||
class="d-none d-sm-block mx-1"
|
class="d-none d-sm-block mx-1"
|
||||||
[routerLink]="['/about']"
|
|
||||||
i18n
|
i18n
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
|
[color]="currentRoute?.startsWith('about') ? 'primary' : null"
|
||||||
|
[routerLink]="['/about']"
|
||||||
>About</a
|
>About</a
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -27,8 +27,9 @@ export class HeaderComponent implements OnChanges {
|
|||||||
@Input() info: InfoItem;
|
@Input() info: InfoItem;
|
||||||
@Input() user: User;
|
@Input() user: User;
|
||||||
|
|
||||||
public hasPermissionToAccessAdminControl: boolean;
|
|
||||||
public hasPermissionForSocialLogin: boolean;
|
public hasPermissionForSocialLogin: boolean;
|
||||||
|
public hasPermissionForSubscription: boolean;
|
||||||
|
public hasPermissionToAccessAdminControl: boolean;
|
||||||
public impersonationId: string;
|
public impersonationId: string;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -49,16 +50,21 @@ export class HeaderComponent implements OnChanges {
|
|||||||
|
|
||||||
public ngOnChanges() {
|
public ngOnChanges() {
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
|
this.hasPermissionForSocialLogin = hasPermission(
|
||||||
|
this.info?.globalPermissions,
|
||||||
|
permissions.enableSocialLogin
|
||||||
|
);
|
||||||
|
|
||||||
|
this.hasPermissionForSubscription = hasPermission(
|
||||||
|
this.info?.globalPermissions,
|
||||||
|
permissions.enableSubscription
|
||||||
|
);
|
||||||
|
|
||||||
this.hasPermissionToAccessAdminControl = hasPermission(
|
this.hasPermissionToAccessAdminControl = hasPermission(
|
||||||
this.user.permissions,
|
this.user.permissions,
|
||||||
permissions.accessAdminControl
|
permissions.accessAdminControl
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasPermissionForSocialLogin = hasPermission(
|
|
||||||
this.info?.globalPermissions,
|
|
||||||
permissions.enableSocialLogin
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public impersonateAccount(aId: string) {
|
public impersonateAccount(aId: string) {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
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 { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.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 { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -16,7 +15,6 @@ import { environment } from '../../../environments/environment';
|
|||||||
})
|
})
|
||||||
export class AboutPageComponent implements OnInit {
|
export class AboutPageComponent implements OnInit {
|
||||||
public baseCurrency = baseCurrency;
|
public baseCurrency = baseCurrency;
|
||||||
public hasPermissionForSubscription: boolean;
|
|
||||||
public isLoggedIn: boolean;
|
public isLoggedIn: boolean;
|
||||||
public lastPublish = environment.lastPublish;
|
public lastPublish = environment.lastPublish;
|
||||||
public user: User;
|
public user: User;
|
||||||
@ -37,15 +35,6 @@ export class AboutPageComponent implements OnInit {
|
|||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.dataService.fetchInfo().subscribe((info) => {
|
|
||||||
this.hasPermissionForSubscription = hasPermission(
|
|
||||||
info.globalPermissions,
|
|
||||||
permissions.enableSubscription
|
|
||||||
);
|
|
||||||
|
|
||||||
this.cd.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isLoggedIn = !!this.tokenStorageService.getToken();
|
this.isLoggedIn = !!this.tokenStorageService.getToken();
|
||||||
|
|
||||||
if (this.isLoggedIn)
|
if (this.isLoggedIn)
|
||||||
|
@ -61,105 +61,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="mb-3 text-center" i18n>Changelog</h3>
|
<h3 class="mb-3 text-center" i18n>Changelog</h3>
|
||||||
|
@ -7,10 +7,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mat-card {
|
.mat-card {
|
||||||
&.active {
|
|
||||||
border-color: rgba(var(--palette-primary-500), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.changelog {
|
&.changelog {
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
markdown {
|
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 {}
|
53
apps/client/src/app/pages/pricing/pricing-page.component.ts
Normal file
53
apps/client/src/app/pages/pricing/pricing-page.component.ts
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
102
apps/client/src/app/pages/pricing/pricing-page.html
Normal file
102
apps/client/src/app/pages/pricing/pricing-page.html
Normal file
@ -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>
|
15
apps/client/src/app/pages/pricing/pricing-page.module.ts
Normal file
15
apps/client/src/app/pages/pricing/pricing-page.module.ts
Normal file
@ -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 {}
|
14
apps/client/src/app/pages/pricing/pricing-page.scss
Normal file
14
apps/client/src/app/pages/pricing/pricing-page.scss
Normal file
@ -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…
x
Reference in New Issue
Block a user