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