mirror of https://github.com/ghostfolio/ghostfolio
Thomas Kaul
3 years ago
committed by
GitHub
8 changed files with 211 additions and 0 deletions
@ -0,0 +1,15 @@ |
|||
import { NgModule } from '@angular/core'; |
|||
import { RouterModule, Routes } from '@angular/router'; |
|||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; |
|||
|
|||
import { FirePageComponent } from './fire-page.component'; |
|||
|
|||
const routes: Routes = [ |
|||
{ path: '', component: FirePageComponent, canActivate: [AuthGuard] } |
|||
]; |
|||
|
|||
@NgModule({ |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class FirePageRoutingModule {} |
@ -0,0 +1,86 @@ |
|||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; |
|||
import { DataService } from '@ghostfolio/client/services/data.service'; |
|||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; |
|||
import { UserService } from '@ghostfolio/client/services/user/user.service'; |
|||
import { User } from '@ghostfolio/common/interfaces'; |
|||
import Big from 'big.js'; |
|||
import { Subject } from 'rxjs'; |
|||
import { takeUntil } from 'rxjs/operators'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
selector: 'gf-fire-page', |
|||
styleUrls: ['./fire-page.scss'], |
|||
templateUrl: './fire-page.html' |
|||
}) |
|||
export class FirePageComponent implements OnDestroy, OnInit { |
|||
public fireWealth: number; |
|||
public hasImpersonationId: boolean; |
|||
public isLoading = false; |
|||
public user: User; |
|||
public withdrawalRatePerMonth: number; |
|||
public withdrawalRatePerYear: number; |
|||
|
|||
private unsubscribeSubject = new Subject<void>(); |
|||
|
|||
/** |
|||
* @constructor |
|||
*/ |
|||
public constructor( |
|||
private changeDetectorRef: ChangeDetectorRef, |
|||
private dataService: DataService, |
|||
private impersonationStorageService: ImpersonationStorageService, |
|||
private userService: UserService |
|||
) {} |
|||
|
|||
/** |
|||
* Initializes the controller |
|||
*/ |
|||
public ngOnInit() { |
|||
this.isLoading = true; |
|||
|
|||
this.impersonationStorageService |
|||
.onChangeHasImpersonation() |
|||
.pipe(takeUntil(this.unsubscribeSubject)) |
|||
.subscribe((aId) => { |
|||
this.hasImpersonationId = !!aId; |
|||
}); |
|||
|
|||
this.dataService |
|||
.fetchPortfolioSummary() |
|||
.pipe(takeUntil(this.unsubscribeSubject)) |
|||
.subscribe(({ cash, currentValue }) => { |
|||
if (cash === null || currentValue === null) { |
|||
return; |
|||
} |
|||
|
|||
this.fireWealth = new Big(currentValue).plus(cash).toNumber(); |
|||
this.withdrawalRatePerYear = new Big(this.fireWealth) |
|||
.mul(4) |
|||
.div(100) |
|||
.toNumber(); |
|||
this.withdrawalRatePerMonth = new Big(this.withdrawalRatePerYear) |
|||
.div(12) |
|||
.toNumber(); |
|||
|
|||
this.isLoading = false; |
|||
|
|||
this.changeDetectorRef.markForCheck(); |
|||
}); |
|||
|
|||
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(); |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
<div class="container"> |
|||
<div class="row"> |
|||
<div class="col-lg"> |
|||
<h3 class="d-flex justify-content-center mb-3" i18n>FIRE</h3> |
|||
<div class="mb-4"> |
|||
<h4 i18n>4% Rule</h4> |
|||
<div *ngIf="isLoading"> |
|||
<ngx-skeleton-loader |
|||
animation="pulse" |
|||
class="my-1" |
|||
[theme]="{ |
|||
height: '1rem', |
|||
width: '100%' |
|||
}" |
|||
></ngx-skeleton-loader> |
|||
<ngx-skeleton-loader |
|||
animation="pulse" |
|||
[theme]="{ |
|||
height: '1rem', |
|||
width: '10rem' |
|||
}" |
|||
></ngx-skeleton-loader> |
|||
</div> |
|||
<div *ngIf="!isLoading"> |
|||
If you retire today, you would be able to withdraw |
|||
<span class="font-weight-bold" |
|||
><gf-value |
|||
class="d-inline-block" |
|||
[currency]="user?.settings?.baseCurrency" |
|||
[value]="withdrawalRatePerYear" |
|||
></gf-value> |
|||
per year</span |
|||
> |
|||
or |
|||
<span class="font-weight-bold" |
|||
><gf-value |
|||
class="d-inline-block" |
|||
[currency]="user?.settings?.baseCurrency" |
|||
[value]="withdrawalRatePerMonth" |
|||
></gf-value> |
|||
per month</span |
|||
>, based on your net worth of |
|||
<gf-value |
|||
class="d-inline-block" |
|||
[currency]="user?.settings?.baseCurrency" |
|||
[value]="fireWealth" |
|||
></gf-value> |
|||
(excluding emergency fund) and a withdrawal rate of 4%. |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
@ -0,0 +1,19 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; |
|||
import { GfValueModule } from '@ghostfolio/ui/value'; |
|||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; |
|||
|
|||
import { FirePageRoutingModule } from './fire-page-routing.module'; |
|||
import { FirePageComponent } from './fire-page.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [FirePageComponent], |
|||
imports: [ |
|||
CommonModule, |
|||
FirePageRoutingModule, |
|||
GfValueModule, |
|||
NgxSkeletonLoaderModule |
|||
], |
|||
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
|||
}) |
|||
export class FirePageModule {} |
@ -0,0 +1,3 @@ |
|||
:host { |
|||
display: block; |
|||
} |
Loading…
Reference in new issue