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