Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
572dcf075a | |||
29cb83d469 | |||
cac73ac111 | |||
02cf4295a9 | |||
78b3328bf7 | |||
e0d6d9e8ca | |||
54310f2214 | |||
1fec49fbc2 |
17
CHANGELOG.md
17
CHANGELOG.md
@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 2.52.0 - 2024-02-16
|
||||
|
||||
### Added
|
||||
|
||||
- Added a loading indicator to the dividend timeline on the analysis page
|
||||
- Added a loading indicator to the investment timeline on the analysis page
|
||||
- Added support for the cryptocurrency _Jupiter_ (`JUP29210-USD`)
|
||||
|
||||
### Changed
|
||||
|
||||
- Divided the content of the Frequently Asked Questions (FAQ) page into three sections: _General_, _Cloud (SaaS)_ and _Self-Hosting_
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue with the X-axis scale of the dividend timeline on the analysis page
|
||||
- Fixed an issue with the X-axis scale of the investment timeline on the analysis page
|
||||
|
||||
## 2.51.0 - 2024-02-12
|
||||
|
||||
### Changed
|
||||
|
@ -226,7 +226,7 @@ export class AdminService {
|
||||
this.prismaService.symbolProfile.count({ where })
|
||||
]);
|
||||
|
||||
let marketData = assetProfiles.map(
|
||||
let marketData: AdminMarketDataItem[] = assetProfiles.map(
|
||||
({
|
||||
_count,
|
||||
assetClass,
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"CYBER24781": "CyberConnect",
|
||||
"JUP29210": "Jupiter",
|
||||
"LUNA1": "Terra",
|
||||
"LUNA2": "Terra",
|
||||
"SGB1": "Songbird",
|
||||
|
@ -370,6 +370,14 @@
|
||||
<loc>https://ghostfol.io/en/faq</loc>
|
||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/saas</loc>
|
||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/self-hosting</loc>
|
||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/features</loc>
|
||||
<lastmod>${currentDate}T00:00:00+00:00</lastmod>
|
||||
|
@ -111,6 +111,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.hasTabs =
|
||||
(this.currentRoute === this.routerLinkAbout[0].slice(1) ||
|
||||
this.currentRoute === this.routerLinkFaq[0].slice(1) ||
|
||||
this.currentRoute === 'account' ||
|
||||
this.currentRoute === 'admin' ||
|
||||
this.currentRoute === 'home' ||
|
||||
@ -120,7 +121,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
|
||||
this.showFooter =
|
||||
(this.currentRoute === 'blog' ||
|
||||
this.currentRoute === this.routerLinkFaq[0].slice(1) ||
|
||||
this.currentRoute === this.routerLinkFeatures[0].slice(1) ||
|
||||
this.currentRoute === this.routerLinkMarkets[0].slice(1) ||
|
||||
this.currentRoute === 'open' ||
|
||||
|
@ -131,50 +131,48 @@
|
||||
<ng-container
|
||||
*ngIf="assetProfile?.countries?.length > 0 || assetProfile?.sectors?.length > 0"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="assetProfile?.countries?.length === 1 && assetProfile?.sectors?.length === 1; else charts"
|
||||
>
|
||||
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #charts>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
@if (assetProfile?.countries?.length === 1 &&
|
||||
assetProfile?.sectors?.length === 1 ) {
|
||||
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
<div *ngIf="assetProfile?.dataSource === 'MANUAL'" class="mt-3">
|
||||
|
@ -1,10 +1,8 @@
|
||||
<div
|
||||
class="align-items-center container d-flex flex-column h-100 justify-content-center overview p-0 position-relative"
|
||||
>
|
||||
<div
|
||||
*ngIf="hasPermissionToCreateOrder && historicalDataItems?.length === 0; else isUserActive"
|
||||
class="justify-content-center row w-100"
|
||||
>
|
||||
@if(hasPermissionToCreateOrder && historicalDataItems?.length === 0) {
|
||||
<div class="justify-content-center row w-100">
|
||||
<div class="col introduction">
|
||||
<h4 i18n>Welcome to Ghostfolio</h4>
|
||||
<p i18n>Ready to take control of your personal finances?</p>
|
||||
@ -60,43 +58,43 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #isUserActive>
|
||||
<div class="row w-100">
|
||||
<div class="col p-0">
|
||||
<div class="chart-container mx-auto position-relative">
|
||||
<gf-line-chart
|
||||
class="position-absolute"
|
||||
symbol="Performance"
|
||||
unit="%"
|
||||
[colorScheme]="user?.settings?.colorScheme"
|
||||
[hidden]="historicalDataItems?.length === 0"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
||||
[showGradient]="true"
|
||||
[showLoader]="false"
|
||||
[showXAxis]="false"
|
||||
[showYAxis]="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-container row mt-1">
|
||||
<div class="col">
|
||||
<gf-portfolio-performance
|
||||
class="pb-4"
|
||||
[deviceType]="deviceType"
|
||||
[errors]="errors"
|
||||
[isAllTimeHigh]="isAllTimeHigh"
|
||||
[isAllTimeLow]="isAllTimeLow"
|
||||
[isLoading]="isLoadingPerformance"
|
||||
} @else {
|
||||
<div class="row w-100">
|
||||
<div class="col p-0">
|
||||
<div class="chart-container mx-auto position-relative">
|
||||
<gf-line-chart
|
||||
class="position-absolute"
|
||||
symbol="Performance"
|
||||
unit="%"
|
||||
[colorScheme]="user?.settings?.colorScheme"
|
||||
[hidden]="historicalDataItems?.length === 0"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[performance]="performance"
|
||||
[showDetails]="showDetails"
|
||||
[unit]="unit"
|
||||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
||||
[showGradient]="true"
|
||||
[showLoader]="false"
|
||||
[showXAxis]="false"
|
||||
[showYAxis]="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="overview-container row mt-1">
|
||||
<div class="col">
|
||||
<gf-portfolio-performance
|
||||
class="pb-4"
|
||||
[deviceType]="deviceType"
|
||||
[errors]="errors"
|
||||
[isAllTimeHigh]="isAllTimeHigh"
|
||||
[isAllTimeLow]="isAllTimeLow"
|
||||
[isLoading]="isLoadingPerformance"
|
||||
[locale]="user?.settings?.locale"
|
||||
[performance]="performance"
|
||||
[showDetails]="showDetails"
|
||||
[unit]="unit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -38,8 +38,16 @@ import {
|
||||
} from 'chart.js';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||
import { addDays, format, isAfter, parseISO, subDays } from 'date-fns';
|
||||
import { last } from 'lodash';
|
||||
import {
|
||||
addDays,
|
||||
format,
|
||||
isAfter,
|
||||
isValid,
|
||||
min,
|
||||
parseISO,
|
||||
subDays
|
||||
} from 'date-fns';
|
||||
import { first, last } from 'lodash';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-investment-chart',
|
||||
@ -143,7 +151,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
const chartData: ChartData<'line'> = {
|
||||
const chartData: ChartData<'bar' | 'line'> = {
|
||||
labels: this.historicalDataItems.map(({ date }) => {
|
||||
return parseDate(date);
|
||||
}),
|
||||
@ -194,17 +202,23 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
||||
};
|
||||
|
||||
if (this.chartCanvas) {
|
||||
let scaleXMin: string;
|
||||
|
||||
if (this.daysInMarket) {
|
||||
const minDate = min([
|
||||
parseDate(first(this.investments)?.date),
|
||||
subDays(new Date().setHours(0, 0, 0, 0), this.daysInMarket)
|
||||
]);
|
||||
|
||||
scaleXMin = isValid(minDate) ? minDate.toISOString() : undefined;
|
||||
}
|
||||
|
||||
if (this.chart) {
|
||||
this.chart.data = chartData;
|
||||
this.chart.options.plugins.tooltip = <unknown>(
|
||||
this.getTooltipPluginConfiguration()
|
||||
);
|
||||
this.chart.options.scales.x.min = this.daysInMarket
|
||||
? subDays(
|
||||
new Date().setHours(0, 0, 0, 0),
|
||||
this.daysInMarket
|
||||
).toISOString()
|
||||
: undefined;
|
||||
this.chart.options.scales.x.min = scaleXMin;
|
||||
|
||||
if (
|
||||
this.savingsRate &&
|
||||
@ -287,9 +301,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
min: this.daysInMarket
|
||||
? subDays(new Date(), this.daysInMarket).toISOString()
|
||||
: undefined,
|
||||
min: scaleXMin,
|
||||
suggestedMax: new Date().toISOString(),
|
||||
type: 'time',
|
||||
time: {
|
||||
|
@ -186,57 +186,52 @@
|
||||
<ng-container
|
||||
*ngIf="SymbolProfile?.countries?.length > 0 || SymbolProfile?.sectors?.length > 0"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="SymbolProfile?.countries?.length === 1 && SymbolProfile?.sectors?.length === 1; else charts"
|
||||
>
|
||||
<div *ngIf="SymbolProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="SymbolProfile?.countries?.length === 1"
|
||||
class="col-6 mb-3"
|
||||
@if(SymbolProfile?.countries?.length === 1 &&
|
||||
SymbolProfile?.sectors?.length === 1) {
|
||||
<div *ngIf="SymbolProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #charts>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="SymbolProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
<div *ngIf="dataProviderInfo" class="col-md-12 mb-3 text-center">
|
||||
<hr />
|
||||
|
@ -3,7 +3,6 @@ import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import * as path from 'path';
|
||||
|
||||
import { AboutPageComponent } from './about-page.component';
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { AboutPageComponent } from './about-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AboutPageComponent],
|
||||
imports: [CommonModule, MatTabsModule, AboutPageRoutingModule, RouterModule],
|
||||
imports: [AboutPageRoutingModule, CommonModule, MatTabsModule, RouterModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AboutPageModule {}
|
||||
|
@ -8,6 +8,27 @@ import { FaqPageComponent } from './faq-page.component';
|
||||
const routes: Routes = [
|
||||
{
|
||||
canActivate: [AuthGuard],
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () =>
|
||||
import('./overview/faq-overview-page.module').then(
|
||||
(m) => m.FaqOverviewPageModule
|
||||
)
|
||||
},
|
||||
{
|
||||
path: 'saas',
|
||||
loadChildren: () =>
|
||||
import('./saas/saas-page.module').then((m) => m.SaasPageModule)
|
||||
},
|
||||
{
|
||||
path: 'self-hosting',
|
||||
loadChildren: () =>
|
||||
import('./self-hosting/self-hosting-page.module').then(
|
||||
(m) => m.SelfHostingPageModule
|
||||
)
|
||||
}
|
||||
],
|
||||
component: FaqPageComponent,
|
||||
path: '',
|
||||
title: $localize`Frequently Asked Questions (FAQ)`
|
||||
|
@ -1,39 +1,57 @@
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { User } from '@ghostfolio/common/interfaces';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { TabConfiguration, User } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
|
||||
import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
host: { class: 'page has-tabs' },
|
||||
selector: 'gf-faq-page',
|
||||
styleUrls: ['./faq-page.scss'],
|
||||
templateUrl: './faq-page.html'
|
||||
})
|
||||
export class FaqPageComponent implements OnDestroy {
|
||||
public routerLinkFeatures = ['/' + $localize`features`];
|
||||
public routerLinkMarkets = ['/' + $localize`markets`];
|
||||
public routerLinkPricing = ['/' + $localize`pricing`];
|
||||
public routerLinkRegister = ['/' + $localize`register`];
|
||||
public user: User;
|
||||
export class FaqPageComponent implements OnDestroy, OnInit {
|
||||
public deviceType: string;
|
||||
public hasPermissionForSubscription: boolean;
|
||||
public tabs: TabConfiguration[] = [];
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private userService: UserService
|
||||
) {}
|
||||
private dataService: DataService,
|
||||
private deviceService: DeviceDetectorService
|
||||
) {
|
||||
const { globalPermissions } = this.dataService.fetchInfo();
|
||||
|
||||
this.hasPermissionForSubscription = hasPermission(
|
||||
globalPermissions,
|
||||
permissions.enableSubscription
|
||||
);
|
||||
|
||||
this.tabs = [
|
||||
{
|
||||
iconName: 'reader-outline',
|
||||
label: $localize`General`,
|
||||
path: ['/' + $localize`faq`]
|
||||
},
|
||||
{
|
||||
iconName: 'cloudy-outline',
|
||||
label: $localize`Cloud` + ' (SaaS)',
|
||||
path: ['/' + $localize`faq`, 'saas'],
|
||||
showCondition: this.hasPermissionForSubscription
|
||||
},
|
||||
{
|
||||
iconName: 'server-outline',
|
||||
label: $localize`Self-Hosting`,
|
||||
path: ['/' + $localize`faq`, $localize`self-hosting`]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
|
@ -1,291 +1,29 @@
|
||||
<div class="container">
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>
|
||||
Frequently Asked Questions (FAQ)
|
||||
</h1>
|
||||
<p>
|
||||
Find quick answers to commonly asked questions about Ghostfolio in our
|
||||
Frequently Asked Questions (FAQ) section. Discover what Ghostfolio is,
|
||||
explore its features, and learn about our privacy practices. Get all the
|
||||
information you need in one place.
|
||||
</p>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>What is Ghostfolio?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
Ghostfolio is a lightweight, open source wealth management application
|
||||
for individuals to keep track of their net worth. The software
|
||||
empowers you to make solid, data-driven investment decisions.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What assets can I track with Ghostfolio?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
With Ghostfolio, you can keep track of various assets like stocks,
|
||||
ETFs, bonds, cryptocurrencies and commodities.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What else is included in Ghostfolio?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content>
|
||||
Please find a feature overview to manage your wealth
|
||||
<a [routerLink]="routerLinkFeatures">here</a>.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>How do I start?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
You can sign up via the “<a [routerLink]="routerLinkRegister"
|
||||
>Get Started</a
|
||||
>” button at the top of the page. You have multiple options to join
|
||||
Ghostfolio: Create an account with a security token or
|
||||
<i>Google Sign</i>. We will guide you to set up your portfolio.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Will you spam me with emails once I sign up?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content>
|
||||
No, we do not even collect your email address, so you will not receive
|
||||
any spam emails from us.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Can I use Ghostfolio anonymously?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content>
|
||||
Yes, the authentication system via security token enables you to sign
|
||||
in securely and anonymously to Ghostfolio. There is no need for an
|
||||
e-mail address, phone number, or a username.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>How can Ghostfolio be free?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>This project is driven by the efforts of contributors from around the
|
||||
world. The
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">source code</a> is
|
||||
fully available as open source software (OSS). Thanks to our generous
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> users and
|
||||
<a href="https://www.buymeacoffee.com/ghostfolio">sponsors</a> we have
|
||||
the ability to run a free, limited plan for novice
|
||||
investors.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Is it really free?</mat-card-title></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>Yes, it is! Our
|
||||
<a [routerLink]="routerLinkPricing">pricing page</a> details
|
||||
everything you get for free.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Do you monetize or sell my financial data?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>No, we value your privacy. We do not sell or share your financial
|
||||
data with any third parties.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What is your business model?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>By offering
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a>, a
|
||||
subscription plan with a managed hosting service and enhanced
|
||||
features, we fund our business while providing added value to our
|
||||
users.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What is Ghostfolio Premium?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
><a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> is a fully
|
||||
managed Ghostfolio cloud offering for ambitious investors. Revenue is
|
||||
used to cover the costs of the hosting infrastructure and to fund
|
||||
ongoing development. It is the Open Source code base with some extras
|
||||
like the <a [routerLink]="routerLinkMarkets">markets overview</a> and
|
||||
a professional data provider.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Can I start with a trial version?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>Yes, you can try
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> by signing
|
||||
up for Ghostfolio and applying for a trial (see “My Ghostfolio”). It
|
||||
is easy, free and there is no commitment. You can stop using it at any
|
||||
time.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>How can I get a student discount for Ghostfolio
|
||||
Premium?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Request your student discount
|
||||
<a href="mailto:hi@ghostfol.io?Subject=Student Discount">here</a> with
|
||||
your university e-mail address.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Does the Ghostfolio Premium subscription renew
|
||||
automatically?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>No, <a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> does
|
||||
not include auto-renewal. Upon expiration, you can choose whether to
|
||||
start a new subscription.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Which devices are supported?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Ghostfolio works in every modern web browser on smartphones, tablets
|
||||
and desktop computers. For <i>Android</i> users, there is a dedicated
|
||||
Ghostfolio app available in the
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
||||
>Google Play Store</a
|
||||
>.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card
|
||||
*ngIf="user?.subscription?.type === 'Premium'"
|
||||
appearance="outlined"
|
||||
class="mb-3"
|
||||
>
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>I cannot find my broker in the list of platforms. What can I
|
||||
do?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
Please send an e-mail with the web address of your broker to
|
||||
<a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a> and we are
|
||||
happy to add it.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Ghostfolio sounds cool, how can I get involved?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Any support for Ghostfolio is welcome. Be it with a
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a>
|
||||
subscription to finance the hosting infrastructure, a positive rating
|
||||
in the
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
||||
>Google Play Store</a
|
||||
>, a star on
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>,
|
||||
feedback, bug reports, feature requests and of course contributions!
|
||||
You can reach us via Ghostfolio
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack</a
|
||||
>
|
||||
community,
|
||||
<a
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>,
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
or
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub</a
|
||||
>.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Got any other questions?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Please join the Ghostfolio
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack </a
|
||||
>community, post to
|
||||
<a
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>, send an e-mail to
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub</a
|
||||
>.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-tab-nav-panel #tabPanel class="flex-grow-1 overflow-auto">
|
||||
<router-outlet></router-outlet>
|
||||
</mat-tab-nav-panel>
|
||||
|
||||
<nav
|
||||
mat-align-tabs="center"
|
||||
mat-tab-nav-bar
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
</nav>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { FaqPageRoutingModule } from './faq-page-routing.module';
|
||||
import { FaqPageComponent } from './faq-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FaqPageComponent],
|
||||
imports: [CommonModule, FaqPageRoutingModule, MatCardModule],
|
||||
imports: [CommonModule, FaqPageRoutingModule, MatTabsModule, RouterModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class FaqPageModule {}
|
||||
|
@ -1,12 +1,7 @@
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
color: rgba(var(--palette-primary-500), 1);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: rgba(var(--palette-primary-300), 1);
|
||||
}
|
||||
}
|
||||
color: rgb(var(--dark-primary-text));
|
||||
}
|
||||
|
||||
:host-context(.is-dark-theme) {
|
||||
color: rgb(var(--light-primary-text));
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { FaqOverviewPageComponent } from './faq-overview-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
canActivate: [AuthGuard],
|
||||
component: FaqOverviewPageComponent,
|
||||
path: '',
|
||||
title: $localize`Frequently Asked Questions (FAQ)`
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FaqOverviewPageRoutingModule {}
|
@ -0,0 +1,41 @@
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { User } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
selector: 'gf-faq-overview-page',
|
||||
styleUrls: ['./faq-overview-page.scss'],
|
||||
templateUrl: './faq-overview-page.html'
|
||||
})
|
||||
export class FaqOverviewPageComponent implements OnDestroy {
|
||||
public routerLinkFeatures = ['/' + $localize`features`];
|
||||
public routerLinkPricing = ['/' + $localize`pricing`];
|
||||
public user: User;
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
public ngOnInit() {
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
}
|
172
apps/client/src/app/pages/faq/overview/faq-overview-page.html
Normal file
172
apps/client/src/app/pages/faq/overview/faq-overview-page.html
Normal file
@ -0,0 +1,172 @@
|
||||
<div class="container">
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>
|
||||
Frequently Asked Questions (FAQ)
|
||||
</h1>
|
||||
<p>
|
||||
Find quick answers to commonly asked questions about Ghostfolio in our
|
||||
Frequently Asked Questions (FAQ) section. Discover what Ghostfolio is,
|
||||
explore its features, and learn about our privacy practices. Get all the
|
||||
information you need in one place.
|
||||
</p>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>What is Ghostfolio?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
Ghostfolio is a lightweight, open source wealth management application
|
||||
for individuals to keep track of their net worth. The software
|
||||
empowers you to make solid, data-driven investment decisions.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What assets can I track with Ghostfolio?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
With Ghostfolio, you can keep track of various assets like stocks,
|
||||
ETFs, bonds, cryptocurrencies and commodities.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What else is included in Ghostfolio?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content>
|
||||
Please find a feature overview to manage your wealth
|
||||
<a [routerLink]="routerLinkFeatures">here</a>.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Can I use Ghostfolio anonymously?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content>
|
||||
Yes, the authentication system via security token enables you to sign
|
||||
in securely and anonymously to Ghostfolio. There is no need for an
|
||||
e-mail address, phone number, or a username.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>How can Ghostfolio be free?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>This project is driven by the efforts of contributors from around the
|
||||
world. The
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">source code</a> is
|
||||
fully available as open source software (OSS). Thanks to our generous
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> users and
|
||||
<a href="https://www.buymeacoffee.com/ghostfolio">sponsors</a> we have
|
||||
the ability to run a free, limited plan for novice
|
||||
investors.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Do you monetize or sell my financial data?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>No, we value your privacy. We do not sell or share your financial
|
||||
data with any third parties.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What is your business model?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>By offering
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a>, a
|
||||
subscription plan with a managed hosting service and enhanced
|
||||
features, we fund our business while providing added value to our
|
||||
users.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Ghostfolio sounds cool, how can I get involved?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Any support for Ghostfolio is welcome. Be it with a
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a>
|
||||
subscription to finance the hosting infrastructure, a positive rating
|
||||
in the
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
||||
>Google Play Store</a
|
||||
>, a star on
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>,
|
||||
feedback, bug reports, feature requests and of course contributions!
|
||||
You can reach us via Ghostfolio
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack</a
|
||||
>
|
||||
community,
|
||||
<a
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>,
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
or
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub</a
|
||||
>.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Got any other questions?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Please join the Ghostfolio
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack </a
|
||||
>community, post to
|
||||
<a
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>, send an e-mail to
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub</a
|
||||
>.</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 { FaqOverviewPageRoutingModule } from './faq-overview-page-routing.module';
|
||||
import { FaqOverviewPageComponent } from './faq-overview-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FaqOverviewPageComponent],
|
||||
imports: [CommonModule, FaqOverviewPageRoutingModule, MatCardModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class FaqOverviewPageModule {}
|
@ -0,0 +1,12 @@
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
color: rgba(var(--palette-primary-500), 1);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: rgba(var(--palette-primary-300), 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SaasPageComponent } from './saas-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
canActivate: [AuthGuard],
|
||||
component: SaasPageComponent,
|
||||
path: '',
|
||||
title: $localize`Cloud` + ' (SaaS) – ' + $localize`FAQ`
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SaasPageRoutingModule {}
|
42
apps/client/src/app/pages/faq/saas/saas-page.component.ts
Normal file
42
apps/client/src/app/pages/faq/saas/saas-page.component.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { User } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
selector: 'gf-saas-page',
|
||||
styleUrls: ['./saas-page.scss'],
|
||||
templateUrl: './saas-page.html'
|
||||
})
|
||||
export class SaasPageComponent implements OnDestroy {
|
||||
public routerLinkMarkets = ['/' + $localize`markets`];
|
||||
public routerLinkPricing = ['/' + $localize`pricing`];
|
||||
public routerLinkRegister = ['/' + $localize`register`];
|
||||
public user: User;
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
public ngOnInit() {
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
}
|
162
apps/client/src/app/pages/faq/saas/saas-page.html
Normal file
162
apps/client/src/app/pages/faq/saas/saas-page.html
Normal file
@ -0,0 +1,162 @@
|
||||
<div class="container">
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>
|
||||
Frequently Asked Questions (FAQ)
|
||||
</h1>
|
||||
<p>
|
||||
Find quick answers to commonly asked questions about the fully managed
|
||||
Ghostfolio cloud offering in our Frequently Asked Questions (FAQ)
|
||||
section.
|
||||
</p>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>How do I start?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
You can sign up via the “<a [routerLink]="routerLinkRegister"
|
||||
>Get Started</a
|
||||
>” button at the top of the page. You have multiple options to join
|
||||
Ghostfolio: Create an account with a security token or
|
||||
<i>Google Sign</i>. We will guide you to set up your portfolio.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Will you spam me with emails once I sign up?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content>
|
||||
No, we do not even collect your email address, so you will not receive
|
||||
any spam emails from us.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Is it really free?</mat-card-title></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>Yes, it is! Our
|
||||
<a [routerLink]="routerLinkPricing">pricing page</a> details
|
||||
everything you get for free.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>What is Ghostfolio Premium?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
><a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> is a fully
|
||||
managed Ghostfolio cloud offering for ambitious investors. Revenue is
|
||||
used to cover the costs of the hosting infrastructure and to fund
|
||||
ongoing development. It is the Open Source code base with some extras
|
||||
like the <a [routerLink]="routerLinkMarkets">markets overview</a> and
|
||||
a professional data provider.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Can I start with a trial version?</mat-card-title
|
||||
></mat-card-header
|
||||
>
|
||||
<mat-card-content
|
||||
>Yes, you can try
|
||||
<a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> by signing
|
||||
up for Ghostfolio and applying for a trial (see “My Ghostfolio”). It
|
||||
is easy, free and there is no commitment. You can stop using it at any
|
||||
time.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>How can I get a student discount for Ghostfolio
|
||||
Premium?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Request your student discount
|
||||
<a href="mailto:hi@ghostfol.io?Subject=Student Discount">here</a> with
|
||||
your university e-mail address.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>Does the Ghostfolio Premium subscription renew
|
||||
automatically?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>No, <a [routerLink]="routerLinkPricing">Ghostfolio Premium</a> does
|
||||
not include auto-renewal. Upon expiration, you can choose whether to
|
||||
start a new subscription.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card
|
||||
*ngIf="user?.subscription?.type === 'Premium'"
|
||||
appearance="outlined"
|
||||
class="mb-3"
|
||||
>
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>I cannot find my broker in the list of platforms. What can I
|
||||
do?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
Please send an e-mail with the web address of your broker to
|
||||
<a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a> and we are
|
||||
happy to add it.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Which devices are supported?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Ghostfolio works in every modern web browser on smartphones, tablets
|
||||
and desktop computers. For <i>Android</i> users of the managed cloud
|
||||
offering, there is a dedicated Ghostfolio app available in the
|
||||
<a
|
||||
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
||||
>Google Play Store</a
|
||||
>.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Got any other questions?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Please join the Ghostfolio
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack </a
|
||||
>community, post to
|
||||
<a
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>, send an e-mail to
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub</a
|
||||
>.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
13
apps/client/src/app/pages/faq/saas/saas-page.module.ts
Normal file
13
apps/client/src/app/pages/faq/saas/saas-page.module.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
|
||||
import { SaasPageRoutingModule } from './saas-page-routing.module';
|
||||
import { SaasPageComponent } from './saas-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SaasPageComponent],
|
||||
imports: [CommonModule, MatCardModule, SaasPageRoutingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SaasPageModule {}
|
12
apps/client/src/app/pages/faq/saas/saas-page.scss
Normal file
12
apps/client/src/app/pages/faq/saas/saas-page.scss
Normal file
@ -0,0 +1,12 @@
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
color: rgba(var(--palette-primary-500), 1);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: rgba(var(--palette-primary-300), 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SelfHostingPageComponent } from './self-hosting-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
canActivate: [AuthGuard],
|
||||
component: SelfHostingPageComponent,
|
||||
path: '',
|
||||
title: $localize`Self-Hosting` + ' – ' + $localize`FAQ`
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SelfHostingPageRoutingModule {}
|
@ -0,0 +1,21 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
selector: 'gf-self-hosting-page',
|
||||
styleUrls: ['./self-hosting-page.scss'],
|
||||
templateUrl: './self-hosting-page.html'
|
||||
})
|
||||
export class SelfHostingPageComponent implements OnDestroy {
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor() {}
|
||||
|
||||
public ngOnInit() {}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<div class="container">
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>
|
||||
Frequently Asked Questions (FAQ)
|
||||
</h1>
|
||||
<p>
|
||||
Find quick answers to commonly asked questions about self-hosting
|
||||
Ghostfolio in our Frequently Asked Questions (FAQ) section.
|
||||
</p>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>How do I start?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
If you prefer to run Ghostfolio on your own infrastructure, please
|
||||
find the source code and further instructions on
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Which devices are supported?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Ghostfolio works in every modern web browser on smartphones, tablets
|
||||
and desktop computers.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Got any other questions?</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content
|
||||
>Please join the Ghostfolio
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack </a
|
||||
>community, post to
|
||||
<a
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
>
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub</a
|
||||
>.</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 { SelfHostingPageRoutingModule } from './self-hosting-page-routing.module';
|
||||
import { SelfHostingPageComponent } from './self-hosting-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SelfHostingPageComponent],
|
||||
imports: [CommonModule, MatCardModule, SelfHostingPageRoutingModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SelfHostingPageModule {}
|
@ -0,0 +1,12 @@
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
a {
|
||||
color: rgba(var(--palette-primary-500), 1);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: rgba(var(--palette-primary-300), 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,84 +24,83 @@
|
||||
>
|
||||
</ng-template>
|
||||
<div class="pt-3">
|
||||
<ng-container *ngIf="mode === 'DIVIDEND'; else selectFile">
|
||||
<form
|
||||
[formGroup]="uniqueAssetForm"
|
||||
(ngSubmit)="onLoadDividends(stepper)"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Holding</mat-label>
|
||||
<mat-select formControlName="uniqueAsset">
|
||||
<mat-select-trigger
|
||||
>{{ uniqueAssetForm.controls['uniqueAsset']?.value?.name
|
||||
}}</mat-select-trigger
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let holding of holdings"
|
||||
class="line-height-1"
|
||||
[value]="{ dataSource: holding.dataSource, name: holding.name, symbol: holding.symbol }"
|
||||
>
|
||||
<span><b>{{ holding.name }}</b></span>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ holding.symbol | gfSymbol }} · {{ holding.currency
|
||||
}}</small
|
||||
>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-spinner
|
||||
*ngIf="isLoading"
|
||||
class="position-absolute"
|
||||
[diameter]="20"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
type="submit"
|
||||
[disabled]="!uniqueAssetForm.valid"
|
||||
@if (mode === 'DIVIDEND') {
|
||||
<form
|
||||
[formGroup]="uniqueAssetForm"
|
||||
(ngSubmit)="onLoadDividends(stepper)"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Holding</mat-label>
|
||||
<mat-select formControlName="uniqueAsset">
|
||||
<mat-select-trigger
|
||||
>{{ uniqueAssetForm.controls['uniqueAsset']?.value?.name
|
||||
}}</mat-select-trigger
|
||||
>
|
||||
<span i18n>Load Dividends</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
<ng-template #selectFile>
|
||||
<mat-option
|
||||
*ngFor="let holding of holdings"
|
||||
class="line-height-1"
|
||||
[value]="{ dataSource: holding.dataSource, name: holding.name, symbol: holding.symbol }"
|
||||
>
|
||||
<span><b>{{ holding.name }}</b></span>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ holding.symbol | gfSymbol }} · {{ holding.currency
|
||||
}}</small
|
||||
>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-spinner
|
||||
*ngIf="isLoading"
|
||||
class="position-absolute"
|
||||
[diameter]="20"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
class="drop-area p-4 text-center text-muted"
|
||||
gfFileDrop
|
||||
(click)="onSelectFile(stepper)"
|
||||
(filesDropped)="onFilesDropped({stepper, files: $event})"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
type="submit"
|
||||
[disabled]="!uniqueAssetForm.valid"
|
||||
>
|
||||
<div
|
||||
class="align-items-center d-flex flex-column justify-content-center"
|
||||
>
|
||||
<ion-icon class="cloud-icon" name="cloud-upload-outline" />
|
||||
<span i18n>Choose or drop a file here</span>
|
||||
</div>
|
||||
<span i18n>Load Dividends</span>
|
||||
</button>
|
||||
<p class="mb-0 mt-3 text-center">
|
||||
<small>
|
||||
<span class="mr-1" i18n
|
||||
>The following file formats are supported:</span
|
||||
>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.csv"
|
||||
target="_blank"
|
||||
>CSV</a
|
||||
>
|
||||
<span class="mx-1" i18n>or</span>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.json"
|
||||
target="_blank"
|
||||
>JSON</a
|
||||
>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
</form>
|
||||
} @else {
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
class="drop-area p-4 text-center text-muted"
|
||||
gfFileDrop
|
||||
(click)="onSelectFile(stepper)"
|
||||
(filesDropped)="onFilesDropped({stepper, files: $event})"
|
||||
>
|
||||
<div
|
||||
class="align-items-center d-flex flex-column justify-content-center"
|
||||
>
|
||||
<ion-icon class="cloud-icon" name="cloud-upload-outline" />
|
||||
<span i18n>Choose or drop a file here</span>
|
||||
</div>
|
||||
</button>
|
||||
<p class="mb-0 mt-3 text-center">
|
||||
<small>
|
||||
<span class="mr-1" i18n
|
||||
>The following file formats are supported:</span
|
||||
>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.csv"
|
||||
target="_blank"
|
||||
>CSV</a
|
||||
>
|
||||
<span class="mx-1" i18n>or</span>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.json"
|
||||
target="_blank"
|
||||
>JSON</a
|
||||
>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
@ -115,79 +114,78 @@
|
||||
>
|
||||
</ng-template>
|
||||
<div class="pt-3">
|
||||
<ng-container *ngIf="errorMessages?.length === 0; else errorMessage">
|
||||
<gf-activities-table
|
||||
*ngIf="importStep === 1"
|
||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data?.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToExportActivities]="false"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data?.user?.settings?.locale"
|
||||
[pageSize]="maxSafeInteger"
|
||||
[showActions]="false"
|
||||
[showCheckbox]="true"
|
||||
[showFooter]="false"
|
||||
[showSymbolColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(selectedActivities)="updateSelection($event)"
|
||||
/>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="!selectedActivities?.length"
|
||||
(click)="onImportActivities()"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-template #errorMessage>
|
||||
<mat-accordion displayMode="flat">
|
||||
<mat-expansion-panel
|
||||
*ngFor="let message of errorMessages; let i = index"
|
||||
[disabled]="!details[i]"
|
||||
>
|
||||
<mat-expansion-panel-header class="pl-1">
|
||||
<mat-panel-title>
|
||||
<div class="d-flex">
|
||||
<div class="align-items-center d-flex mr-2">
|
||||
<ion-icon name="warning-outline" />
|
||||
</div>
|
||||
<div>{{ message }}</div>
|
||||
@if(errorMessages?.length === 0) {
|
||||
<gf-activities-table
|
||||
*ngIf="importStep === 1"
|
||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data?.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToExportActivities]="false"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data?.user?.settings?.locale"
|
||||
[pageSize]="maxSafeInteger"
|
||||
[showActions]="false"
|
||||
[showCheckbox]="true"
|
||||
[showFooter]="false"
|
||||
[showSymbolColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(selectedActivities)="updateSelection($event)"
|
||||
/>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="!selectedActivities?.length"
|
||||
(click)="onImportActivities()"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<mat-accordion displayMode="flat">
|
||||
<mat-expansion-panel
|
||||
*ngFor="let message of errorMessages; let i = index"
|
||||
[disabled]="!details[i]"
|
||||
>
|
||||
<mat-expansion-panel-header class="pl-1">
|
||||
<mat-panel-title>
|
||||
<div class="d-flex">
|
||||
<div class="align-items-center d-flex mr-2">
|
||||
<ion-icon name="warning-outline" />
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<pre
|
||||
*ngIf="details[i]"
|
||||
class="m-0"
|
||||
><code>{{ details[i] | json }}</code></pre>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="true"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div>{{ message }}</div>
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<pre
|
||||
*ngIf="details[i]"
|
||||
class="m-0"
|
||||
><code>{{ details[i] | json }}</code></pre>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="true"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-step>
|
||||
</mat-stepper>
|
||||
|
@ -46,7 +46,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
public investmentTimelineDataLabel = $localize`Investment`;
|
||||
public investmentsByGroup: InvestmentItem[];
|
||||
public isLoadingBenchmarkComparator: boolean;
|
||||
public isLoadingDividendTimelineChart: boolean;
|
||||
public isLoadingInvestmentChart: boolean;
|
||||
public isLoadingInvestmentTimelineChart: boolean;
|
||||
public mode: GroupBy = 'month';
|
||||
public modeOptions: ToggleOption[] = [
|
||||
{ label: $localize`Monthly`, value: 'month' },
|
||||
@ -154,6 +156,9 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
private fetchDividendsAndInvestments() {
|
||||
this.isLoadingDividendTimelineChart = true;
|
||||
this.isLoadingInvestmentTimelineChart = true;
|
||||
|
||||
this.dataService
|
||||
.fetchDividends({
|
||||
filters: this.userService.getFilters(),
|
||||
@ -164,6 +169,8 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
.subscribe(({ dividends }) => {
|
||||
this.dividendsByGroup = dividends;
|
||||
|
||||
this.isLoadingDividendTimelineChart = false;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
|
||||
@ -194,6 +201,8 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
||||
? translate('MONTH')
|
||||
: translate('MONTHS');
|
||||
|
||||
this.isLoadingInvestmentTimelineChart = false;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
@ -294,6 +294,7 @@
|
||||
[daysInMarket]="daysInMarket"
|
||||
[groupBy]="mode"
|
||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||
[isLoading]="isLoadingInvestmentTimelineChart"
|
||||
[locale]="user?.settings?.locale"
|
||||
[range]="user?.settings?.dateRange"
|
||||
[savingsRate]="savingsRate"
|
||||
@ -331,6 +332,7 @@
|
||||
[daysInMarket]="daysInMarket"
|
||||
[groupBy]="mode"
|
||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||
[isLoading]="isLoadingDividendTimelineChart"
|
||||
[locale]="user?.settings?.locale"
|
||||
[range]="user?.settings?.dateRange"
|
||||
/>
|
||||
|
@ -48,11 +48,9 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
.fetchPortfolioDetails()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ summary }) => {
|
||||
if (summary.cash === null || summary.currentValue === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fireWealth = new Big(summary.fireWealth);
|
||||
this.fireWealth = summary.fireWealth
|
||||
? new Big(summary.fireWealth)
|
||||
: new Big(10000);
|
||||
this.withdrawalRatePerYear = this.fireWealth.mul(4).div(100);
|
||||
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);
|
||||
|
||||
@ -94,10 +92,13 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
||||
permissions.createOrder
|
||||
);
|
||||
|
||||
this.hasPermissionToUpdateUserSettings = hasPermission(
|
||||
this.user.permissions,
|
||||
permissions.updateUserSettings
|
||||
);
|
||||
this.hasPermissionToUpdateUserSettings =
|
||||
this.user.subscription?.type === 'Basic'
|
||||
? false
|
||||
: hasPermission(
|
||||
this.user.permissions,
|
||||
permissions.updateUserSettings
|
||||
);
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
@ -18,6 +18,10 @@
|
||||
[fireWealth]="fireWealth?.toNumber()"
|
||||
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
|
||||
[locale]="user?.settings?.locale"
|
||||
[ngStyle]="{
|
||||
opacity: user?.subscription?.type === 'Basic' ? '0.67' : 'initial',
|
||||
'pointer-events': user?.subscription?.type === 'Basic' ? 'none' : 'initial'
|
||||
}"
|
||||
[projectedTotalAmount]="user?.settings?.projectedTotalAmount"
|
||||
[retirementDate]="user?.settings?.retirementDate"
|
||||
[savingsRate]="user?.settings?.savingsRate"
|
||||
@ -54,7 +58,11 @@
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div *ngIf="!isLoading" i18n>
|
||||
<div
|
||||
*ngIf="!isLoading"
|
||||
i18n
|
||||
[ngClass]="{ 'text-muted': user?.subscription?.type === 'Basic' }"
|
||||
>
|
||||
If you retire today, you would be able to withdraw
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
|
@ -6,6 +6,7 @@ export interface AdminMarketData {
|
||||
}
|
||||
|
||||
export interface AdminMarketDataItem {
|
||||
activitiesCount?: number;
|
||||
assetClass?: AssetClass;
|
||||
assetSubClass?: AssetSubClass;
|
||||
countriesCount: number;
|
||||
|
@ -13,7 +13,7 @@
|
||||
.mdc-text-field--disabled {
|
||||
.mdc-floating-label,
|
||||
.mdc-text-field__input {
|
||||
color: inherit;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.mdc-notched-outline__leading,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "2.51.0",
|
||||
"version": "2.52.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||
|
Reference in New Issue
Block a user