Browse Source

Feature/add support for configuring safe withdrawal rate (#5679)

* Add support for configuring safe withdrawal rate

* Update changelog
pull/5723/head
Shivansh Pandey 4 weeks ago
committed by GitHub
parent
commit
94e8a7c6bc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 59
      apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
  3. 17
      apps/client/src/app/pages/portfolio/fire/fire-page.html
  4. 16
      apps/client/src/app/pages/portfolio/fire/fire-page.scss

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added support for configuring the safe withdrawal rate in the _FIRE_ section (experimental)
### Changed
- Changed the _As seen in_ section on the landing page to an animated carousel

59
apps/client/src/app/pages/portfolio/fire/fire-page.component.ts

@ -7,8 +7,10 @@ import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { NgStyle } from '@angular/common';
import { CommonModule, NgStyle } from '@angular/common';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { Big } from 'big.js';
import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -17,11 +19,14 @@ import { takeUntil } from 'rxjs/operators';
@Component({
imports: [
CommonModule,
FormsModule,
GfFireCalculatorComponent,
GfPremiumIndicatorComponent,
GfValueComponent,
NgStyle,
NgxSkeletonLoaderModule
NgxSkeletonLoaderModule,
ReactiveFormsModule
],
selector: 'gf-fire-page',
styleUrls: ['./fire-page.scss'],
@ -33,6 +38,8 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
public hasImpersonationId: boolean;
public hasPermissionToUpdateUserSettings: boolean;
public isLoading = false;
public safeWithdrawalRateControl = new FormControl<number>(undefined);
public safeWithdrawalRateOptions = [0.025, 0.03, 0.035, 0.04, 0.045];
public user: User;
public withdrawalRatePerMonth: Big;
public withdrawalRatePerYear: Big;
@ -70,11 +77,7 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
};
}
this.withdrawalRatePerYear = Big(
this.fireWealth.today.valueInBaseCurrency
).mul(this.user.settings.safeWithdrawalRate);
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);
this.calculateWithdrawalRates();
this.isLoading = false;
@ -88,6 +91,12 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
this.hasImpersonationId = !!impersonationId;
});
this.safeWithdrawalRateControl.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((value) => {
this.onSafeWithdrawalRateChange(Number(value));
});
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
@ -102,6 +111,13 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
permissions.updateUserSettings
);
this.safeWithdrawalRateControl.setValue(
this.user.settings.safeWithdrawalRate,
{ emitEvent: false }
);
this.calculateWithdrawalRates();
this.changeDetectorRef.markForCheck();
}
});
@ -141,6 +157,25 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
});
});
}
public onSafeWithdrawalRateChange(safeWithdrawalRate: number) {
this.dataService
.putUserSetting({ safeWithdrawalRate })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.userService
.get(true)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((user) => {
this.user = user;
this.calculateWithdrawalRates();
this.changeDetectorRef.markForCheck();
});
});
}
public onSavingsRateChange(savingsRate: number) {
this.dataService
.putUserSetting({ savingsRate })
@ -180,4 +215,14 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private calculateWithdrawalRates() {
if (this.fireWealth && this.user?.settings?.safeWithdrawalRate) {
this.withdrawalRatePerYear = new Big(
this.fireWealth.today.valueInBaseCurrency
).mul(this.user.settings.safeWithdrawalRate);
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);
}
}
}

17
apps/client/src/app/pages/portfolio/fire/fire-page.html

@ -105,6 +105,22 @@
</span>
<ng-container>&nbsp;</ng-container>
<ng-container i18n>and a safe withdrawal rate (SWR) of</ng-container>
@if (
!hasImpersonationId &&
hasPermissionToUpdateUserSettings &&
user?.settings?.isExperimentalFeatures
) {
<select
class="border-0 cursor-pointer d-inline-block font-weight-bold safe-withdrawal-rate-select"
[formControl]="safeWithdrawalRateControl"
>
@for (rate of safeWithdrawalRateOptions; track rate) {
<option [value]="rate">
{{ rate | percent: '1.1-1' }}
</option>
}</select
>.
} @else {
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold"
><gf-value
@ -114,6 +130,7 @@
[precision]="1"
[value]="user?.settings?.safeWithdrawalRate" /></span
>.
}
</div>
}
</div>

16
apps/client/src/app/pages/portfolio/fire/fire-page.scss

@ -1,3 +1,19 @@
:host {
display: block;
.safe-withdrawal-rate-select {
background-color: transparent;
color: rgb(var(--dark-primary-text));
&:focus {
box-shadow: none;
outline: 0;
}
}
}
:host-context(.theme-dark) {
.safe-withdrawal-rate-select {
color: rgb(var(--light-primary-text));
}
}

Loading…
Cancel
Save