Feature/setup personal finance tools pages (#2135)
parent
ba220eaee9
commit
2d23c566f1
@ -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,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