mirror of https://github.com/ghostfolio/ghostfolio
Thomas Kaul
2 years ago
committed by
GitHub
33 changed files with 1410 additions and 103 deletions
@ -0,0 +1,34 @@ |
|||
import { NgModule } from '@angular/core'; |
|||
import { RouterModule, Routes } from '@angular/router'; |
|||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; |
|||
|
|||
import { PersonalFinanceToolsPageComponent } from './personal-finance-tools-page.component'; |
|||
import { products } from './products'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
canActivate: [AuthGuard], |
|||
component: PersonalFinanceToolsPageComponent, |
|||
path: '', |
|||
title: $localize`Personal Finance Tools` |
|||
}, |
|||
...products |
|||
.filter(({ key }) => { |
|||
return key !== 'ghostfolio'; |
|||
}) |
|||
.map(({ component, key, name }) => { |
|||
return { |
|||
canActivate: [AuthGuard], |
|||
path: `open-source-alternative-to-${key}`, |
|||
loadComponent: () => |
|||
import(`./products/${key}-page.component`).then(() => component), |
|||
title: `Open Source Alternative to ${name}` |
|||
}; |
|||
}) |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class PersonalFinanceToolsPageRoutingModule {} |
@ -0,0 +1,25 @@ |
|||
import { Component, OnDestroy } from '@angular/core'; |
|||
import { Subject } from 'rxjs'; |
|||
|
|||
import { products } from './products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
selector: 'gf-personal-finance-tools-page', |
|||
styleUrls: ['./personal-finance-tools-page.scss'], |
|||
templateUrl: './personal-finance-tools-page.html' |
|||
}) |
|||
export class PersonalFinanceToolsPageComponent implements OnDestroy { |
|||
public products = products.filter(({ key }) => { |
|||
return key !== 'ghostfolio'; |
|||
}); |
|||
|
|||
private unsubscribeSubject = new Subject<void>(); |
|||
|
|||
public constructor() {} |
|||
|
|||
public ngOnDestroy() { |
|||
this.unsubscribeSubject.next(); |
|||
this.unsubscribeSubject.complete(); |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
<div class="container"> |
|||
<div class="mb-5 row"> |
|||
<div class="col"> |
|||
<h3 class="d-none d-sm-block mb-3 text-center" i18n> |
|||
Discover Open Source Alternatives for Personal Finance Tools |
|||
</h3> |
|||
<div class="introduction mb-4"> |
|||
<p> |
|||
This overview page features a curated collection of personal finance |
|||
tools compared to the open source alternative |
|||
<a [routerLink]="['/about']">Ghostfolio</a>. If you value |
|||
transparency, data privacy, and community collaboration, Ghostfolio |
|||
provides an excellent opportunity to take control of your financial |
|||
management. |
|||
</p> |
|||
<p> |
|||
Explore the links below to compare a variety of personal finance tools |
|||
with Ghostfolio. |
|||
</p> |
|||
</div> |
|||
<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" |
|||
title="Compare Ghostfolio to {{ product.name }}" |
|||
[routerLink]="['/resources', 'personal-finance-tools', 'open-source-alternative-to-' + product.key]" |
|||
> |
|||
<div class="flex-grow-1 overflow-hidden"> |
|||
<div class="h6 m-0 text-truncate"> |
|||
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 { PersonalFinanceToolsPageRoutingModule } from './personal-finance-tools-page-routing.module'; |
|||
import { PersonalFinanceToolsPageComponent } from './personal-finance-tools-page.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [PersonalFinanceToolsPageComponent], |
|||
imports: [CommonModule, MatCardModule, PersonalFinanceToolsPageRoutingModule], |
|||
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
|||
}) |
|||
export class PersonalFinanceToolsPageModule {} |
@ -0,0 +1,19 @@ |
|||
:host { |
|||
color: rgb(var(--dark-primary-text)); |
|||
display: block; |
|||
|
|||
.introduction { |
|||
a { |
|||
color: rgba(var(--palette-primary-500), 1); |
|||
font-weight: 500; |
|||
|
|||
&:hover { |
|||
color: rgba(var(--palette-primary-300), 1); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
:host-context(.is-dark-theme) { |
|||
color: rgb(var(--light-primary-text)); |
|||
} |
@ -0,0 +1,263 @@ |
|||
<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"> |
|||
<strong>Ghostfolio</strong>: The Open Source Alternative to |
|||
<strong>{{ product2.name }}</strong> |
|||
</h1> |
|||
</div> |
|||
<section class="mb-4"> |
|||
<p> |
|||
Are you looking for an open source alternative to {{ product2.name |
|||
}}? <a [routerLink]="['/about']">Ghostfolio</a> is a powerful |
|||
portfolio management tool that provides individuals with a |
|||
comprehensive platform to track, analyze, and optimize their |
|||
investments. Whether you are an experienced investor or just |
|||
starting out, Ghostfolio offers an intuitive user interface and a |
|||
<a [routerLink]="['/features']">wide range of functionalities</a> |
|||
to help you make informed decisions and take control of your |
|||
financial future. |
|||
</p> |
|||
<p> |
|||
Ghostfolio is open source software (OSS) where a community of |
|||
developers, contributors, and enthusiasts collaborate to enhance its |
|||
capabilities, security, and user experience. |
|||
</p> |
|||
<p> |
|||
Let’s dive deeper into the detailed comparison table below to gain a |
|||
thorough understanding of how Ghostfolio positions itself relative |
|||
to {{ product2.name }}. We will explore various aspects such as |
|||
features, data privacy, pricing, and more, allowing you to make a |
|||
well-informed choice for your personal requirements. |
|||
</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-3 py-2 text-right"></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-3 py-2 text-right" 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-3 py-2 text-right" 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-3 py-2 text-right" 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-3 py-2 text-right" 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-3 py-2 text-right" i18n> |
|||
Open Source Software |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container *ngIf="product1.isOpenSource === true" i18n |
|||
>✅ Yes</ng-container |
|||
><ng-container *ngIf="product1.isOpenSource === false" i18n |
|||
>❌ No</ng-container |
|||
> |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container *ngIf="product2.isOpenSource === true" i18n |
|||
>✅ Yes</ng-container |
|||
><ng-container *ngIf="product2.isOpenSource === false" i18n |
|||
>❌ No |
|||
</ng-container> |
|||
</td> |
|||
</tr> |
|||
<tr class="mat-mdc-row"> |
|||
<td class="mat-mdc-cell px-3 py-2 text-right" i18n> |
|||
Self-Hosting |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container |
|||
*ngIf="product1.hasSelfHostingAbility === true" |
|||
i18n |
|||
>✅ Yes</ng-container |
|||
><ng-container |
|||
*ngIf="product1.hasSelfHostingAbility === false" |
|||
i18n |
|||
>❌ No</ng-container |
|||
> |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container |
|||
*ngIf="product2.hasSelfHostingAbility === true" |
|||
i18n |
|||
>✅ Yes</ng-container |
|||
><ng-container |
|||
*ngIf="product2.hasSelfHostingAbility === false" |
|||
i18n |
|||
>❌ No</ng-container |
|||
> |
|||
</td> |
|||
</tr> |
|||
<tr class="mat-mdc-row"> |
|||
<td class="mat-mdc-cell px-3 py-2 text-right" i18n> |
|||
Free Plan |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container *ngIf="product1.hasFreePlan === true" i18n |
|||
>✅ Yes</ng-container |
|||
><ng-container *ngIf="product1.hasFreePlan === false" i18n |
|||
>❌ No</ng-container |
|||
> |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container *ngIf="product2.hasFreePlan === true" i18n |
|||
>✅ Yes</ng-container |
|||
><ng-container *ngIf="product2.hasFreePlan === false" i18n |
|||
>❌ No</ng-container |
|||
> |
|||
</td> |
|||
</tr> |
|||
<tr class="mat-mdc-row"> |
|||
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Pricing</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
Starting from {{ product1.pricingPerYear }} / year |
|||
</td> |
|||
<td class="mat-mdc-cell px-1 py-2"> |
|||
<ng-container *ngIf="product2.pricingPerYear" |
|||
>Starting from {{ product2.pricingPerYear }} / |
|||
year</ng-container |
|||
> |
|||
</td> |
|||
</tr> |
|||
<tr *ngIf="product1.note || product2.note" class="mat-mdc-row"> |
|||
<td class="mat-mdc-cell px-3 py-2 text-right" 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"> |
|||
Ready to take your <strong>investments</strong> to the |
|||
<strong>next level</strong>? |
|||
</h2> |
|||
<p class="lead mb-2 text-center" i18n> |
|||
Effortlessly track, analyze, and visualize your wealth with |
|||
Ghostfolio. |
|||
</p> |
|||
<div class="text-center"> |
|||
<a color="primary" href="https://ghostfol.io" mat-flat-button> |
|||
Get Started |
|||
</a> |
|||
</div> |
|||
</section> |
|||
<section class="mb-4"> |
|||
<small> |
|||
Please note that the information provided is based on our |
|||
independent research and analysis. This website is not affiliated |
|||
with {{ product2.name }} or any other product mentioned in the |
|||
comparison. As the landscape of personal finance tools evolves, it |
|||
is essential to verify any specific details or changes directly from |
|||
the respective product page. Data needs a refresh? Help us maintain |
|||
accurate data on |
|||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>. |
|||
</small> |
|||
</section> |
|||
<section class="mb-4"> |
|||
<ul class="list-inline"> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">{{ product1.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">Alternative</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">App</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Community</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Fintech</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Investment</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Investor</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">OSS</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Personal Finance</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Privacy</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Portfolio</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Software</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Tool</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">User Experience</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Wealth</span> |
|||
</li> |
|||
<li class="list-inline-item"> |
|||
<span class="badge badge-light">Wealth Management</span> |
|||
</li> |
|||
</ul> |
|||
</section> |
|||
<nav aria-label="breadcrumb"> |
|||
<ol class="breadcrumb"> |
|||
<li class="breadcrumb-item"> |
|||
<a i18n [routerLink]="['/resources', 'personal-finance-tools']" |
|||
>Personal Finance Tools</a |
|||
> |
|||
</li> |
|||
<li |
|||
aria-current="page" |
|||
class="active breadcrumb-item text-truncate" |
|||
> |
|||
{{ product2.name }} |
|||
</li> |
|||
</ol> |
|||
</nav> |
|||
</article> |
|||
</div> |
|||
</div> |
|||
</div> |
@ -0,0 +1,17 @@ |
|||
:host { |
|||
color: rgb(var(--dark-primary-text)); |
|||
display: block; |
|||
|
|||
a { |
|||
color: rgba(var(--palette-primary-500), 1); |
|||
font-weight: 500; |
|||
|
|||
&:hover { |
|||
color: rgba(var(--palette-primary-300), 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
:host-context(.is-dark-theme) { |
|||
color: rgb(var(--light-primary-text)); |
|||
} |
@ -0,0 +1,280 @@ |
|||
import { Product } from '@ghostfolio/common/interfaces'; |
|||
|
|||
import { AltooPageComponent } from './products/altoo-page.component'; |
|||
import { DivvyDiaryPageComponent } from './products/divvydiary-page.component'; |
|||
import { ExirioPageComponent } from './products/exirio-page.component'; |
|||
import { FolisharePageComponent } from './products/folishare-page.component'; |
|||
import { GetquinPageComponent } from './products/getquin-page.component'; |
|||
import { JustEtfPageComponent } from './products/justetf-page.component'; |
|||
import { KuberaPageComponent } from './products/kubera-page.component'; |
|||
import { MaybeFinancePageComponent } from './products/maybe-finance-page.component'; |
|||
import { MonsePageComponent } from './products/monse-page.component'; |
|||
import { ParqetPageComponent } from './products/parqet-page.component'; |
|||
import { PortfolioDividendTrackerPageComponent } from './products/portfolio-dividend-tracker-page.component'; |
|||
import { PortseidoPageComponent } from './products/portseido-page.component'; |
|||
import { SeekingAlphaPageComponent } from './products/seeking-alpha-page.component'; |
|||
import { SharesightPageComponent } from './products/sharesight-page.component'; |
|||
import { SimplePortfolioPageComponent } from './products/simple-portfolio-page.component'; |
|||
import { SnowballAnalyticsPageComponent } from './products/snowball-analytics-page.component'; |
|||
import { SumioPageComponent } from './products/sumio-page.component'; |
|||
import { UtlunaPageComponent } from './products/utluna-page.component'; |
|||
import { YeekateePageComponent } from './products/yeekatee-page.component'; |
|||
import { ProjectionLabPageComponent } from './products/projectionlab-page.component'; |
|||
|
|||
export const products: Product[] = [ |
|||
{ |
|||
component: undefined, |
|||
founded: 2021, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: true, |
|||
isOpenSource: true, |
|||
key: 'ghostfolio', |
|||
languages: 'Dutch, English, French, German, Italian, Portuguese, Spanish', |
|||
name: 'Ghostfolio', |
|||
origin: 'Switzerland', |
|||
pricingPerYear: '$19', |
|||
region: 'Global', |
|||
slogan: 'Open Source Wealth Management' |
|||
}, |
|||
{ |
|||
component: AltooPageComponent, |
|||
founded: 2017, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'altoo', |
|||
name: 'Altoo Wealth Platform', |
|||
origin: 'Switzerland', |
|||
slogan: 'Simplicity for Complex Wealth' |
|||
}, |
|||
{ |
|||
component: DivvyDiaryPageComponent, |
|||
founded: 2019, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'divvydiary', |
|||
languages: 'English, German', |
|||
name: 'DivvyDiary', |
|||
origin: 'Germany', |
|||
pricingPerYear: '€65', |
|||
slogan: 'Your personal Dividend Calendar' |
|||
}, |
|||
{ |
|||
component: ExirioPageComponent, |
|||
founded: 2020, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'exirio', |
|||
name: 'Exirio', |
|||
origin: 'United States', |
|||
pricingPerYear: '$100', |
|||
slogan: 'All your wealth, in one place.' |
|||
}, |
|||
{ |
|||
component: FolisharePageComponent, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'folishare', |
|||
languages: 'English, German', |
|||
name: 'folishare', |
|||
origin: 'Austria', |
|||
pricingPerYear: '$65', |
|||
slogan: 'Take control over your investments' |
|||
}, |
|||
{ |
|||
component: GetquinPageComponent, |
|||
founded: 2020, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'getquin', |
|||
languages: 'English, German', |
|||
name: 'getquin', |
|||
origin: 'Germany', |
|||
pricingPerYear: '€48', |
|||
slogan: 'Portfolio Tracker, Analysis & Community' |
|||
}, |
|||
{ |
|||
component: JustEtfPageComponent, |
|||
founded: 2011, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'justetf', |
|||
name: 'justETF', |
|||
origin: 'Germany', |
|||
pricingPerYear: '€119', |
|||
slogan: 'ETF portfolios made simple' |
|||
}, |
|||
{ |
|||
component: KuberaPageComponent, |
|||
founded: 2019, |
|||
hasFreePlan: false, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'kubera', |
|||
name: 'Kubera®', |
|||
origin: 'United States', |
|||
pricingPerYear: '$150', |
|||
slogan: 'The Time Machine for your Net Worth' |
|||
}, |
|||
{ |
|||
component: MaybeFinancePageComponent, |
|||
founded: 2021, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'maybe-finance', |
|||
languages: 'English', |
|||
name: 'Maybe Finance', |
|||
note: 'Sunset in 2023', |
|||
origin: 'United States', |
|||
pricingPerYear: '$145', |
|||
region: 'United States', |
|||
slogan: 'Your financial future, in your control' |
|||
}, |
|||
{ |
|||
component: MonsePageComponent, |
|||
hasFreePlan: false, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'monse', |
|||
name: 'Monse', |
|||
pricingPerYear: '$60', |
|||
slogan: 'Gain financial control and keep your data private.' |
|||
}, |
|||
{ |
|||
component: ParqetPageComponent, |
|||
founded: 2020, |
|||
hasSelfHostingAbility: false, |
|||
hasFreePlan: true, |
|||
isOpenSource: false, |
|||
key: 'parqet', |
|||
name: 'Parqet', |
|||
note: 'Originally named as Tresor One', |
|||
origin: 'Germany', |
|||
pricingPerYear: '€88', |
|||
region: 'Austria, Germany, Switzerland', |
|||
slogan: 'Dein Vermögen immer im Blick' |
|||
}, |
|||
{ |
|||
component: PortfolioDividendTrackerPageComponent, |
|||
hasFreePlan: false, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'portfolio-dividend-tracker', |
|||
languages: 'English, Dutch', |
|||
name: 'Portfolio Dividend Tracker', |
|||
origin: 'Netherlands', |
|||
pricingPerYear: '€60', |
|||
slogan: 'Manage all your portfolios' |
|||
}, |
|||
{ |
|||
component: PortseidoPageComponent, |
|||
founded: 2021, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'portseido', |
|||
languages: 'Dutch, English, French, German', |
|||
name: 'Portseido', |
|||
origin: 'Thailand', |
|||
pricingPerYear: '$96', |
|||
slogan: 'Portfolio Performance and Dividend Tracker' |
|||
}, |
|||
{ |
|||
component: ProjectionLabPageComponent, |
|||
founded: 2021, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: true, |
|||
isOpenSource: false, |
|||
key: 'projectionlab', |
|||
name: 'ProjectionLab', |
|||
origin: 'United States', |
|||
pricingPerYear: '$108', |
|||
slogan: 'Build Financial Plans You Love.' |
|||
}, |
|||
{ |
|||
component: SeekingAlphaPageComponent, |
|||
founded: 2004, |
|||
hasFreePlan: false, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'seeking-alpha', |
|||
name: 'Seeking Alpha', |
|||
origin: 'United States', |
|||
pricingPerYear: '$239', |
|||
slogan: 'Stock Market Analysis & Tools for Investors' |
|||
}, |
|||
{ |
|||
component: SharesightPageComponent, |
|||
founded: 2007, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'sharesight', |
|||
name: 'Sharesight', |
|||
origin: 'New Zealand', |
|||
pricingPerYear: '$135', |
|||
region: 'Global', |
|||
slogan: 'Stock Portfolio Tracker' |
|||
}, |
|||
{ |
|||
component: SimplePortfolioPageComponent, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'simple-portfolio', |
|||
name: 'Simple Portfolio', |
|||
origin: 'Czech Republic', |
|||
pricingPerYear: '€80', |
|||
slogan: 'Stock Portfolio Tracker' |
|||
}, |
|||
{ |
|||
component: SnowballAnalyticsPageComponent, |
|||
founded: 2021, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'snowball-analytics', |
|||
name: 'Snowball Analytics', |
|||
origin: 'France', |
|||
pricingPerYear: '$80', |
|||
slogan: 'Simple and powerful portfolio tracker' |
|||
}, |
|||
{ |
|||
component: SumioPageComponent, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'sumio', |
|||
name: 'Sumio', |
|||
origin: 'Czech Republic', |
|||
pricingPerYear: '$20', |
|||
slogan: 'Sum up and build your wealth.' |
|||
}, |
|||
{ |
|||
component: UtlunaPageComponent, |
|||
hasFreePlan: true, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'utluna', |
|||
languages: 'English, French, German', |
|||
name: 'Utluna', |
|||
origin: 'Switzerland', |
|||
pricingPerYear: '$300', |
|||
slogan: 'Your Portfolio. Revealed.' |
|||
}, |
|||
{ |
|||
component: YeekateePageComponent, |
|||
founded: 2021, |
|||
hasSelfHostingAbility: false, |
|||
isOpenSource: false, |
|||
key: 'yeekatee', |
|||
name: 'yeekatee', |
|||
origin: 'Switzerland', |
|||
region: 'Switzerland', |
|||
slogan: 'Connect. Share. Invest.' |
|||
} |
|||
]; |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-altoo-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class AltooPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'altoo'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-divvy-diary-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class DivvyDiaryPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'divvydiary'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-exirio-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class ExirioPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'exirio'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-folishare-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class FolisharePageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'folishare'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-getquin-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class GetquinPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'getquin'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-justetf-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class JustEtfPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'justetf'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-kubera-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class KuberaPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'kubera'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-maybe-finance-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class MaybeFinancePageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'maybe-finance'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-monse-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class MonsePageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'monse'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-parqet-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class ParqetPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-portfolio-dividend-tracker-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class PortfolioDividendTrackerPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'portfolio-dividend-tracker'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-portseido-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class PortseidoPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'portseido'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-projection-lab-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class ProjectionLabPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'projectionlab'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-seeking-alpha-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class SeekingAlphaPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'seeking-alpha'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-sharesight-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class SharesightPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'sharesight'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-simple-portfolio-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class SimplePortfolioPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'simple-portfolio'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-snowball-analytics-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class SnowballAnalyticsPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'snowball-analytics'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-sumio-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class SumioPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'sumio'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-utluna-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class UtlunaPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'utluna'; |
|||
}); |
|||
} |
@ -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 { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-yeekatee-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class YeekateePageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'yeekatee'; |
|||
}); |
|||
} |
@ -0,0 +1,15 @@ |
|||
export interface Product { |
|||
component: any; |
|||
founded?: number; |
|||
hasFreePlan?: boolean; |
|||
hasSelfHostingAbility?: boolean; |
|||
isOpenSource: boolean; |
|||
key: string; |
|||
languages?: string; |
|||
name: string; |
|||
note?: string; |
|||
origin?: string; |
|||
pricingPerYear?: string; |
|||
region?: string; |
|||
slogan?: string; |
|||
} |
Loading…
Reference in new issue