mirror of https://github.com/ghostfolio/ghostfolio
14 changed files with 426 additions and 1 deletions
@ -0,0 +1,48 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { RouterModule, Routes } from '@angular/router'; |
||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; |
||||
|
|
||||
|
import { AlternativesPageComponent } from './alternatives-page.component'; |
||||
|
import { data } from './data'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ |
||||
|
canActivate: [AuthGuard], |
||||
|
component: AlternativesPageComponent, |
||||
|
path: '', |
||||
|
title: $localize`Alternatives` |
||||
|
}, |
||||
|
{ |
||||
|
canActivate: [AuthGuard], |
||||
|
path: 'maybe', |
||||
|
loadComponent: () => |
||||
|
import('./products/maybe-page.component').then( |
||||
|
(c) => c.MaybePageComponent |
||||
|
), |
||||
|
title: data.find(({ key }) => key === 'maybe').name |
||||
|
}, |
||||
|
{ |
||||
|
canActivate: [AuthGuard], |
||||
|
path: 'parqet', |
||||
|
loadComponent: () => |
||||
|
import('./products/parqet-page.component').then( |
||||
|
(c) => c.ParqetPageComponent |
||||
|
), |
||||
|
title: data.find(({ key }) => key === 'parqet').name |
||||
|
}, |
||||
|
{ |
||||
|
canActivate: [AuthGuard], |
||||
|
path: 'yeekatee', |
||||
|
loadComponent: () => |
||||
|
import('./products/yeekatee-page.component').then( |
||||
|
(c) => c.YeekateePageComponent |
||||
|
), |
||||
|
title: data.find(({ key }) => key === 'yeekatee').name |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule] |
||||
|
}) |
||||
|
export class AlternativesPageRoutingModule {} |
@ -0,0 +1,25 @@ |
|||||
|
import { Component, OnDestroy } from '@angular/core'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
|
||||
|
import { data } from './data'; |
||||
|
|
||||
|
@Component({ |
||||
|
host: { class: 'page' }, |
||||
|
selector: 'gf-alternatives-page', |
||||
|
styleUrls: ['./alternatives-page.scss'], |
||||
|
templateUrl: './alternatives-page.html' |
||||
|
}) |
||||
|
export class AlternativesPageComponent implements OnDestroy { |
||||
|
public products = data.filter(({ key }) => { |
||||
|
return key !== 'ghostfolio'; |
||||
|
}); |
||||
|
|
||||
|
private unsubscribeSubject = new Subject<void>(); |
||||
|
|
||||
|
public constructor() {} |
||||
|
|
||||
|
public ngOnDestroy() { |
||||
|
this.unsubscribeSubject.next(); |
||||
|
this.unsubscribeSubject.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
<div class="container"> |
||||
|
<div class="mb-5 row"> |
||||
|
<div class="col"> |
||||
|
<h3 class="d-none d-sm-block mb-3 text-center" i18n>Alternatives</h3> |
||||
|
<mat-card |
||||
|
*ngFor="let product of products" |
||||
|
appearance="outlined" |
||||
|
class="mb-3" |
||||
|
> |
||||
|
<mat-card-content> |
||||
|
<div class="container p-0"> |
||||
|
<div class="flex-nowrap no-gutters row"> |
||||
|
<a |
||||
|
class="d-flex overflow-hidden w-100" |
||||
|
[routerLink]="['/alternatives', product.key]" |
||||
|
> |
||||
|
<div class="flex-grow-1 overflow-hidden"> |
||||
|
<div class="h6 m-0 text-truncate"> |
||||
|
Ghostfolio: The open source alternative to {{ product.name |
||||
|
}} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="align-items-center d-flex"> |
||||
|
<ion-icon |
||||
|
class="chevron text-muted" |
||||
|
name="chevron-forward-outline" |
||||
|
size="small" |
||||
|
></ion-icon> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</mat-card-content> |
||||
|
</mat-card> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
@ -0,0 +1,13 @@ |
|||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; |
||||
|
import { MatCardModule } from '@angular/material/card'; |
||||
|
|
||||
|
import { AlternativesPageRoutingModule } from './alternatives-page-routing.module'; |
||||
|
import { AlternativesPageComponent } from './alternatives-page.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [AlternativesPageComponent], |
||||
|
imports: [AlternativesPageRoutingModule, CommonModule, MatCardModule], |
||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
||||
|
}) |
||||
|
export class AlternativesPageModule {} |
@ -0,0 +1,8 @@ |
|||||
|
:host { |
||||
|
color: rgb(var(--dark-primary-text)); |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
:host-context(.is-dark-theme) { |
||||
|
color: rgb(var(--light-primary-text)); |
||||
|
} |
@ -0,0 +1,47 @@ |
|||||
|
import { Comparison } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
export const data: Comparison[] = [ |
||||
|
{ |
||||
|
founded: 2021, |
||||
|
hasFreePlan: true, |
||||
|
isOpenSource: true, |
||||
|
key: 'ghostfolio', |
||||
|
languages: |
||||
|
'English, Dutch, French, German, Italian, Portuguese and Spanish', |
||||
|
name: 'Ghostfolio', |
||||
|
origin: 'Switzerland', |
||||
|
pricing: 'Starting from $19 / year', |
||||
|
region: 'Global', |
||||
|
slogan: 'Open Source Wealth Management' |
||||
|
}, |
||||
|
{ |
||||
|
founded: 2021, |
||||
|
isOpenSource: false, |
||||
|
key: 'maybe', |
||||
|
languages: 'English', |
||||
|
name: 'Maybe Finance', |
||||
|
note: 'Sunset in 2023', |
||||
|
origin: 'USA', |
||||
|
pricing: 'Starting from $145 / year', |
||||
|
region: 'USA', |
||||
|
slogan: 'Your financial future, in your control' |
||||
|
}, |
||||
|
{ |
||||
|
hasFreePlan: true, |
||||
|
isOpenSource: false, |
||||
|
key: 'parqet', |
||||
|
name: 'Parqet', |
||||
|
origin: 'Germany', |
||||
|
region: 'DACH', |
||||
|
slogan: 'Dein Vermögen immer im Blick' |
||||
|
}, |
||||
|
{ |
||||
|
founded: 2021, |
||||
|
isOpenSource: false, |
||||
|
key: 'yeekatee', |
||||
|
name: 'yeekatee', |
||||
|
origin: 'Switzerland', |
||||
|
region: 'CH', |
||||
|
slogan: 'Connect. Share. Invest.' |
||||
|
} |
||||
|
]; |
@ -0,0 +1,151 @@ |
|||||
|
<div class="container"> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-8 offset-md-2"> |
||||
|
<article> |
||||
|
<div class="mb-4 text-center"> |
||||
|
<h1 class="mb-1"> |
||||
|
Ghostfolio: The open source alternative to {{ product2.name }} |
||||
|
</h1> |
||||
|
</div> |
||||
|
<section class="mb-4"> |
||||
|
<p> |
||||
|
Are you looking for an open source alternative to {{ product2.name |
||||
|
}}? Compare Ghostfolio to {{ product2.name }} using the table below. |
||||
|
</p> |
||||
|
</section> |
||||
|
<section class="mb-4"> |
||||
|
<table class="gf-table w-100"> |
||||
|
<thead> |
||||
|
<tr class="mat-mdc-header-row"> |
||||
|
<th class="mat-mdc-header-cell px-1 py-2"></th> |
||||
|
<th class="mat-mdc-header-cell px-1 py-2">Ghostfolio</th> |
||||
|
<th class="mat-mdc-header-cell px-1 py-2"> |
||||
|
{{ product2.name }} |
||||
|
</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2"></td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.slogan }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.slogan }}</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Founded</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.founded }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.founded }}</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Origin</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.origin }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.origin }}</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Region</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.region }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.region }}</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Available in</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.languages }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.languages }}</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n> |
||||
|
Open Source Software |
||||
|
</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2"> |
||||
|
<ng-container *ngIf="product1.isOpenSource === true" |
||||
|
>✅</ng-container |
||||
|
><ng-container *ngIf="product1.isOpenSource === false" |
||||
|
>❌</ng-container |
||||
|
> |
||||
|
</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2"> |
||||
|
<ng-container *ngIf="product2.isOpenSource === true" |
||||
|
>✅</ng-container |
||||
|
><ng-container *ngIf="product2.isOpenSource === false" |
||||
|
>❌</ng-container |
||||
|
> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Free Plan</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2"> |
||||
|
<ng-container *ngIf="product1.hasFreePlan === true" |
||||
|
>✅</ng-container |
||||
|
><ng-container *ngIf="product1.hasFreePlan === false" |
||||
|
>❌</ng-container |
||||
|
> |
||||
|
</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2"> |
||||
|
<ng-container *ngIf="product2.hasFreePlan === true" |
||||
|
>✅</ng-container |
||||
|
><ng-container *ngIf="product2.hasFreePlan === false" |
||||
|
>❌</ng-container |
||||
|
> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Pricing</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.pricing }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.pricing }}</td> |
||||
|
</tr> |
||||
|
<tr *ngIf="product1.note || product2.note" class="mat-mdc-row"> |
||||
|
<td class="mat-mdc-cell px-1 py-2" i18n>Notes</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product1.note }}</td> |
||||
|
<td class="mat-mdc-cell px-1 py-2">{{ product2.note }}</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</section> |
||||
|
<section class="mb-4 py-3"> |
||||
|
<h2 class="h4 mb-0 text-center"> |
||||
|
Would you like to <strong>refine</strong> your |
||||
|
<strong>personal investment strategy</strong>? |
||||
|
</h2> |
||||
|
<p class="lead mb-2 text-center" i18n> |
||||
|
Ghostfolio empowers you to keep track of your wealth. |
||||
|
</p> |
||||
|
<div class="text-center"> |
||||
|
<a color="primary" href="https://ghostfol.io" mat-flat-button> |
||||
|
Get Started |
||||
|
</a> |
||||
|
</div> |
||||
|
</section> |
||||
|
<section class="mb-4"> |
||||
|
<ul class="list-inline"> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">App</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">{{ product2.name }}</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">{{ product2.name }}</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Open Source</span> |
||||
|
</li> |
||||
|
<li class="list-inline-item"> |
||||
|
<span class="badge badge-light">Personal Finance</span> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</section> |
||||
|
<nav aria-label="breadcrumb"> |
||||
|
<ol class="breadcrumb"> |
||||
|
<li class="breadcrumb-item"> |
||||
|
<a i18n [routerLink]="['/alternatives']">Alternatives</a> |
||||
|
</li> |
||||
|
<li |
||||
|
aria-current="page" |
||||
|
class="active breadcrumb-item text-truncate" |
||||
|
> |
||||
|
{{ product2.name }} |
||||
|
</li> |
||||
|
</ol> |
||||
|
</nav> |
||||
|
</article> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
@ -0,0 +1,24 @@ |
|||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { Component } from '@angular/core'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
import { Comparison } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
import { data } from '../data'; |
||||
|
|
||||
|
@Component({ |
||||
|
host: { class: 'page' }, |
||||
|
imports: [CommonModule, MatButtonModule, RouterModule], |
||||
|
selector: 'gf-maybe-page', |
||||
|
standalone: true, |
||||
|
templateUrl: '../page-template.html' |
||||
|
}) |
||||
|
export class MaybePageComponent { |
||||
|
public product1: Comparison = data.find(({ key }) => { |
||||
|
return key === 'ghostfolio'; |
||||
|
}); |
||||
|
|
||||
|
public product2: Comparison = data.find(({ key }) => { |
||||
|
return key === 'maybe'; |
||||
|
}); |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { Component } from '@angular/core'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
import { Comparison } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
import { data } from '../data'; |
||||
|
|
||||
|
@Component({ |
||||
|
host: { class: 'page' }, |
||||
|
imports: [CommonModule, MatButtonModule, RouterModule], |
||||
|
selector: 'gf-parqet-page', |
||||
|
standalone: true, |
||||
|
templateUrl: '../page-template.html' |
||||
|
}) |
||||
|
export class ParqetPageComponent { |
||||
|
public product1: Comparison = data.find(({ key }) => { |
||||
|
return key === 'ghostfolio'; |
||||
|
}); |
||||
|
|
||||
|
public product2: Comparison = data.find(({ key }) => { |
||||
|
return key === 'parqet'; |
||||
|
}); |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { Component } from '@angular/core'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
import { Comparison } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
import { data } from '../data'; |
||||
|
|
||||
|
@Component({ |
||||
|
host: { class: 'page' }, |
||||
|
imports: [CommonModule, MatButtonModule, RouterModule], |
||||
|
selector: 'gf-yeekatee-page', |
||||
|
standalone: true, |
||||
|
templateUrl: '../page-template.html' |
||||
|
}) |
||||
|
export class YeekateePageComponent { |
||||
|
public product1: Comparison = data.find(({ key }) => { |
||||
|
return key === 'ghostfolio'; |
||||
|
}); |
||||
|
|
||||
|
public product2: Comparison = data.find(({ key }) => { |
||||
|
return key === 'yeekatee'; |
||||
|
}); |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
export interface Comparison { |
||||
|
founded?: number; |
||||
|
hasFreePlan?: boolean; |
||||
|
isOpenSource: boolean; |
||||
|
key: string; |
||||
|
languages?: string; |
||||
|
name: string; |
||||
|
note?: string; |
||||
|
origin?: string; |
||||
|
pricing?: string; |
||||
|
region?: string; |
||||
|
slogan?: string; |
||||
|
} |
Loading…
Reference in new issue