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 3 months 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. 35
      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 ## Unreleased
### Added
- Added support for configuring the safe withdrawal rate in the _FIRE_ section (experimental)
### Changed ### Changed
- Changed the _As seen in_ section on the landing page to an animated carousel - 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 { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value'; 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 { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormControl } from '@angular/forms';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -17,11 +19,14 @@ import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
imports: [ imports: [
CommonModule,
FormsModule,
GfFireCalculatorComponent, GfFireCalculatorComponent,
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
GfValueComponent, GfValueComponent,
NgStyle, NgStyle,
NgxSkeletonLoaderModule NgxSkeletonLoaderModule,
ReactiveFormsModule
], ],
selector: 'gf-fire-page', selector: 'gf-fire-page',
styleUrls: ['./fire-page.scss'], styleUrls: ['./fire-page.scss'],
@ -33,6 +38,8 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public isLoading = false; 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 user: User;
public withdrawalRatePerMonth: Big; public withdrawalRatePerMonth: Big;
public withdrawalRatePerYear: Big; public withdrawalRatePerYear: Big;
@ -70,11 +77,7 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
}; };
} }
this.withdrawalRatePerYear = Big( this.calculateWithdrawalRates();
this.fireWealth.today.valueInBaseCurrency
).mul(this.user.settings.safeWithdrawalRate);
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);
this.isLoading = false; this.isLoading = false;
@ -88,6 +91,12 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
this.hasImpersonationId = !!impersonationId; this.hasImpersonationId = !!impersonationId;
}); });
this.safeWithdrawalRateControl.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((value) => {
this.onSafeWithdrawalRateChange(Number(value));
});
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
@ -102,6 +111,13 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
permissions.updateUserSettings permissions.updateUserSettings
); );
this.safeWithdrawalRateControl.setValue(
this.user.settings.safeWithdrawalRate,
{ emitEvent: false }
);
this.calculateWithdrawalRates();
this.changeDetectorRef.markForCheck(); 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) { public onSavingsRateChange(savingsRate: number) {
this.dataService this.dataService
.putUserSetting({ savingsRate }) .putUserSetting({ savingsRate })
@ -180,4 +215,14 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); 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);
}
}
} }

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

@ -105,15 +105,32 @@
</span> </span>
<ng-container>&nbsp;</ng-container> <ng-container>&nbsp;</ng-container>
<ng-container i18n>and a safe withdrawal rate (SWR) of</ng-container> <ng-container i18n>and a safe withdrawal rate (SWR) of</ng-container>
<ng-container>&nbsp;</ng-container> @if (
<span class="font-weight-bold" !hasImpersonationId &&
><gf-value hasPermissionToUpdateUserSettings &&
class="d-inline-block" user?.settings?.isExperimentalFeatures
[isPercent]="true" ) {
[locale]="user?.settings?.locale" <select
[precision]="1" class="border-0 cursor-pointer d-inline-block font-weight-bold safe-withdrawal-rate-select"
[value]="user?.settings?.safeWithdrawalRate" /></span [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
class="d-inline-block"
[isPercent]="true"
[locale]="user?.settings?.locale"
[precision]="1"
[value]="user?.settings?.safeWithdrawalRate" /></span
>.
}
</div> </div>
} }
</div> </div>

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

@ -1,3 +1,19 @@
:host { :host {
display: block; 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