Browse Source

Merge branch 'main' into feature/custom_swr

pull/5679/head
Shivansh Pandey 1 month ago
committed by GitHub
parent
commit
805b537cb7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      CHANGELOG.md
  2. 1
      apps/api/src/app/portfolio/portfolio.controller.ts
  3. 8
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 7
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  5. 2
      apps/client/src/app/app-routing.module.ts
  6. 4
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  7. 2
      apps/client/src/app/components/home-market/home-market.component.ts
  8. 4
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts
  9. 8
      apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html
  10. 11
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  11. 1
      apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts
  12. 2
      apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts
  13. 133
      apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html
  14. 4
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  15. 4
      apps/client/src/app/components/user-account-access/user-account-access.component.ts
  16. 5
      apps/client/src/app/directives/file-drop/file-drop.directive.ts
  17. 9
      apps/client/src/app/directives/file-drop/file-drop.module.ts
  18. 8
      apps/client/src/app/pages/home/home-page.routes.ts
  19. 4
      apps/client/src/app/pages/markets/markets-page.component.ts
  20. 22
      apps/client/src/app/pages/open/open-page-routing.module.ts
  21. 20
      apps/client/src/app/pages/open/open-page.component.ts
  22. 20
      apps/client/src/app/pages/open/open-page.module.ts
  23. 15
      apps/client/src/app/pages/open/open-page.routes.ts
  24. 22
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  25. 4
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  26. 14
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  27. 22
      apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
  28. 4
      apps/client/src/app/pages/portfolio/fire/fire-page.html
  29. 9
      apps/client/src/app/pipes/symbol/symbol.module.ts
  30. 5
      apps/client/src/app/pipes/symbol/symbol.pipe.ts
  31. 2
      apps/client/src/locales/messages.de.xlf
  32. 3
      libs/common/src/lib/interfaces/fire-wealth.interface.ts
  33. 2
      libs/common/src/lib/interfaces/index.ts
  34. 3
      libs/common/src/lib/interfaces/portfolio-summary.interface.ts
  35. 4
      libs/ui/src/lib/activities-filter/activities-filter.component.ts
  36. 4
      libs/ui/src/lib/activities-table/activities-table.component.ts
  37. 4
      libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts
  38. 4
      libs/ui/src/lib/assistant/assistant.component.ts
  39. 1
      libs/ui/src/lib/fire-calculator/fire-calculator.component.html
  40. 2
      libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
  41. 4
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  42. 4
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts
  43. 4
      libs/ui/src/lib/top-holdings/top-holdings.component.ts
  44. 96
      package-lock.json
  45. 6
      package.json

25
CHANGELOG.md

@ -5,6 +5,31 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Localized the number formatting in the settings dialog to customize the rule thresholds of the _X-ray_ page
- Improved the usability of the _Cancel_ / _Close_ buttons in the create watchlist item dialog
- Refactored the `fireWealth` from `number` type to a structured object in the summary of the portfolio details endpoint
- Refactored the _Open Startup_ (`/open`) page to standalone
- Refactored the file drop directive to standalone
- Refactored the symbol pipe to standalone
### Fixed
- Handled an exception in the get asset profile functionality of the _Financial Modeling Prep_ service
- Added the missing `CommonModule` import in the import activities dialog
## 2.205.0 - 2025-10-01
### Changed
- Restricted the selection of the retirement date picker in the _FIRE_ calculator to a future date
- Improved the support for mutual funds in the _Financial Modeling Prep_ service (get asset profiles)
- Improved the language localization for German (`de`)
- Upgraded `prisma` from version `6.16.1` to `6.16.3`
## 2.204.0 - 2025-09-30 ## 2.204.0 - 2025-09-30
### Added ### Added

1
apps/api/src/app/portfolio/portfolio.controller.ts

@ -195,7 +195,6 @@ export class PortfolioController {
'excludedAccountsAndActivities', 'excludedAccountsAndActivities',
'fees', 'fees',
'filteredValueInBaseCurrency', 'filteredValueInBaseCurrency',
'fireWealth',
'grossPerformance', 'grossPerformance',
'grossPerformanceWithCurrencyEffect', 'grossPerformanceWithCurrencyEffect',
'interest', 'interest',

8
apps/api/src/app/portfolio/portfolio.service.ts

@ -2092,9 +2092,13 @@ export class PortfolioService {
filteredValueInPercentage: netWorth filteredValueInPercentage: netWorth
? filteredValueInBaseCurrency.div(netWorth).toNumber() ? filteredValueInBaseCurrency.div(netWorth).toNumber()
: undefined, : undefined,
fireWealth: new Big(currentValueInBaseCurrency) fireWealth: {
today: {
valueInBaseCurrency: new Big(currentValueInBaseCurrency)
.minus(emergencyFundHoldingsValueInBaseCurrency) .minus(emergencyFundHoldingsValueInBaseCurrency)
.toNumber(), .toNumber()
}
},
grossPerformance: new Big(netPerformance).plus(fees).toNumber(), grossPerformance: new Big(netPerformance).plus(fees).toNumber(),
grossPerformanceWithCurrencyEffect: new Big( grossPerformanceWithCurrencyEffect: new Big(
netPerformanceWithCurrencyEffect netPerformanceWithCurrencyEffect

7
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -106,7 +106,10 @@ export class FinancialModelingPrepService implements DataProviderInterface {
response.assetClass = assetClass; response.assetClass = assetClass;
response.assetSubClass = assetSubClass; response.assetSubClass = assetSubClass;
if (assetSubClass === AssetSubClass.ETF) { if (
assetSubClass === AssetSubClass.ETF ||
assetSubClass === AssetSubClass.MUTUALFUND
) {
const etfCountryWeightings = await fetch( const etfCountryWeightings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/etf/country-weightings?symbol=${symbol}&apikey=${this.apiKey}`,
{ {
@ -158,7 +161,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} }
).then((res) => res.json()); ).then((res) => res.json());
if (etfInformation.website) { if (etfInformation?.website) {
response.url = etfInformation.website; response.url = etfInformation.website;
} }

2
apps/client/src/app/app-routing.module.ts

@ -94,7 +94,7 @@ const routes: Routes = [
{ {
path: publicRoutes.openStartup.path, path: publicRoutes.openStartup.path,
loadChildren: () => loadChildren: () =>
import('./pages/open/open-page.module').then((m) => m.OpenPageModule) import('./pages/open/open-page.routes').then((m) => m.routes)
}, },
{ {
path: internalRoutes.portfolio.path, path: internalRoutes.portfolio.path,

4
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
@ -79,7 +79,7 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in
CommonModule, CommonModule,
GfActivitiesFilterComponent, GfActivitiesFilterComponent,
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
GfSymbolModule, GfSymbolPipe,
GfValueComponent, GfValueComponent,
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,

2
apps/client/src/app/components/home-market/home-market.component.ts

@ -35,7 +35,7 @@ import { takeUntil } from 'rxjs/operators';
styleUrls: ['./home-market.scss'], styleUrls: ['./home-market.scss'],
templateUrl: './home-market.html' templateUrl: './home-market.html'
}) })
export class HomeMarketComponent implements OnDestroy, OnInit { export class GfHomeMarketComponent implements OnDestroy, OnInit {
public benchmarks: Benchmark[]; public benchmarks: Benchmark[];
public deviceType: string; public deviceType: string;
public fearAndGreedIndex: number; public fearAndGreedIndex: number;

4
apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts

@ -36,13 +36,13 @@ import { Subject } from 'rxjs';
styleUrls: ['./create-watchlist-item-dialog.component.scss'], styleUrls: ['./create-watchlist-item-dialog.component.scss'],
templateUrl: 'create-watchlist-item-dialog.html' templateUrl: 'create-watchlist-item-dialog.html'
}) })
export class CreateWatchlistItemDialogComponent implements OnInit, OnDestroy { export class GfCreateWatchlistItemDialogComponent implements OnInit, OnDestroy {
public createWatchlistItemForm: FormGroup; public createWatchlistItemForm: FormGroup;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
public readonly dialogRef: MatDialogRef<CreateWatchlistItemDialogComponent>, public readonly dialogRef: MatDialogRef<GfCreateWatchlistItemDialogComponent>,
public readonly formBuilder: FormBuilder public readonly formBuilder: FormBuilder
) {} ) {}

8
apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.html

@ -12,7 +12,13 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="d-flex justify-content-end" mat-dialog-actions> <div class="d-flex justify-content-end" mat-dialog-actions>
<button i18n mat-button type="button" (click)="onCancel()">Cancel</button> <button mat-button type="button" (click)="onCancel()">
@if (createWatchlistItemForm.dirty) {
<ng-container i18n>Cancel</ng-container>
} @else {
<ng-container i18n>Close</ng-container>
}
</button>
<button <button
color="primary" color="primary"
mat-flat-button mat-flat-button

11
apps/client/src/app/components/home-watchlist/home-watchlist.component.ts

@ -28,7 +28,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { CreateWatchlistItemDialogComponent } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component'; import { GfCreateWatchlistItemDialogComponent } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component';
import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces'; import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces';
@Component({ @Component({
@ -45,7 +45,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
styleUrls: ['./home-watchlist.scss'], styleUrls: ['./home-watchlist.scss'],
templateUrl: './home-watchlist.html' templateUrl: './home-watchlist.html'
}) })
export class HomeWatchlistComponent implements OnDestroy, OnInit { export class GfHomeWatchlistComponent implements OnDestroy, OnInit {
public deviceType: string; public deviceType: string;
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasPermissionToCreateWatchlistItem: boolean; public hasPermissionToCreateWatchlistItem: boolean;
@ -149,14 +149,17 @@ export class HomeWatchlistComponent implements OnDestroy, OnInit {
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
const dialogRef = this.dialog.open(CreateWatchlistItemDialogComponent, { const dialogRef = this.dialog.open(
GfCreateWatchlistItemDialogComponent,
{
autoFocus: false, autoFocus: false,
data: { data: {
deviceType: this.deviceType, deviceType: this.deviceType,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
} as CreateWatchlistItemDialogParams, } as CreateWatchlistItemDialogParams,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); }
);
dialogRef dialogRef
.afterClosed() .afterClosed()

1
apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts

@ -5,6 +5,7 @@ import {
export interface IRuleSettingsDialogParams { export interface IRuleSettingsDialogParams {
categoryName: string; categoryName: string;
locale: string;
rule: PortfolioReportRule; rule: PortfolioReportRule;
settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment'];
} }

2
apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts

@ -1,4 +1,5 @@
import { XRayRulesSettings } from '@ghostfolio/common/interfaces'; import { XRayRulesSettings } from '@ghostfolio/common/interfaces';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
@ -17,6 +18,7 @@ import { IRuleSettingsDialogParams } from './interfaces/interfaces';
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
GfValueComponent,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,
MatSliderModule MatSliderModule

133
apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html

@ -5,28 +5,30 @@
data.rule.configuration.thresholdMin && data.rule.configuration.thresholdMax data.rule.configuration.thresholdMin && data.rule.configuration.thresholdMax
) { ) {
<div class="w-100"> <div class="w-100">
<h6 class="mb-0"> <h6 class="d-flex mb-0">
<ng-container i18n>Threshold range</ng-container>: <ng-container i18n>Threshold range</ng-container>:
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
{{ data.settings.thresholdMin | percent: '1.2-2' }} class="ml-1"
} @else { [isPercent]="data.rule.configuration.threshold.unit === '%'"
{{ data.settings.thresholdMin }} [locale]="data.locale"
} [precision]="2"
- [value]="data.settings.thresholdMin"
@if (data.rule.configuration.threshold.unit === '%') { />
{{ data.settings.thresholdMax | percent: '1.2-2' }} <span class="mx-1">-</span>
} @else { <gf-value
{{ data.settings.thresholdMax }} [isPercent]="data.rule.configuration.threshold.unit === '%'"
} [locale]="data.locale"
[precision]="2"
[value]="data.settings.thresholdMax"
/>
</h6> </h6>
<div class="align-items-center d-flex w-100"> <div class="align-items-center d-flex w-100">
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
<label>{{ [isPercent]="data.rule.configuration.threshold.unit === '%'"
data.rule.configuration.threshold.min | percent: '1.2-2' [locale]="data.locale"
}}</label> [precision]="2"
} @else { [value]="data.rule.configuration.threshold.min"
<label>{{ data.rule.configuration.threshold.min }}</label> />
}
<mat-slider <mat-slider
class="flex-grow-1" class="flex-grow-1"
[max]="data.rule.configuration.threshold.max" [max]="data.rule.configuration.threshold.max"
@ -36,13 +38,12 @@
<input matSliderStartThumb [(ngModel)]="data.settings.thresholdMin" /> <input matSliderStartThumb [(ngModel)]="data.settings.thresholdMin" />
<input matSliderEndThumb [(ngModel)]="data.settings.thresholdMax" /> <input matSliderEndThumb [(ngModel)]="data.settings.thresholdMax" />
</mat-slider> </mat-slider>
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
<label>{{ [isPercent]="data.rule.configuration.threshold.unit === '%'"
data.rule.configuration.threshold.max | percent: '1.2-2' [locale]="data.locale"
}}</label> [precision]="2"
} @else { [value]="data.rule.configuration.threshold.max"
<label>{{ data.rule.configuration.threshold.max }}</label> />
}
</div> </div>
</div> </div>
} @else { } @else {
@ -50,22 +51,23 @@
class="w-100" class="w-100"
[ngClass]="{ 'd-none': !data.rule.configuration.thresholdMin }" [ngClass]="{ 'd-none': !data.rule.configuration.thresholdMin }"
> >
<h6 class="mb-0"> <h6 class="d-flex mb-0">
<ng-container i18n>Threshold Min</ng-container>: <ng-container i18n>Threshold Min</ng-container>:
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
{{ data.settings.thresholdMin | percent: '1.2-2' }} class="ml-1"
} @else { [isPercent]="data.rule.configuration.threshold.unit === '%'"
{{ data.settings.thresholdMin }} [locale]="data.locale"
} [precision]="2"
[value]="data.settings.thresholdMin"
/>
</h6> </h6>
<div class="align-items-center d-flex w-100"> <div class="align-items-center d-flex w-100">
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
<label>{{ [isPercent]="data.rule.configuration.threshold.unit === '%'"
data.rule.configuration.threshold.min | percent: '1.2-2' [locale]="data.locale"
}}</label> [precision]="2"
} @else { [value]="data.rule.configuration.threshold.min"
<label>{{ data.rule.configuration.threshold.min }}</label> />
}
<mat-slider <mat-slider
class="flex-grow-1" class="flex-grow-1"
name="thresholdMin" name="thresholdMin"
@ -75,35 +77,35 @@
> >
<input matSliderThumb [(ngModel)]="data.settings.thresholdMin" /> <input matSliderThumb [(ngModel)]="data.settings.thresholdMin" />
</mat-slider> </mat-slider>
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
<label>{{ [isPercent]="data.rule.configuration.threshold.unit === '%'"
data.rule.configuration.threshold.max | percent: '1.2-2' [locale]="data.locale"
}}</label> [precision]="2"
} @else { [value]="data.rule.configuration.threshold.max"
<label>{{ data.rule.configuration.threshold.max }}</label> />
}
</div> </div>
</div> </div>
<div <div
class="w-100" class="w-100"
[ngClass]="{ 'd-none': !data.rule.configuration.thresholdMax }" [ngClass]="{ 'd-none': !data.rule.configuration.thresholdMax }"
> >
<h6 class="mb-0"> <h6 class="d-flex mb-0">
<ng-container i18n>Threshold Max</ng-container>: <ng-container i18n>Threshold Max</ng-container>:
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
{{ data.settings.thresholdMax | percent: '1.2-2' }} class="ml-1"
} @else { [isPercent]="data.rule.configuration.threshold.unit === '%'"
{{ data.settings.thresholdMax }} [locale]="data.locale"
} [precision]="2"
[value]="data.settings.thresholdMax"
/>
</h6> </h6>
<div class="align-items-center d-flex w-100"> <div class="align-items-center d-flex w-100">
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
<label>{{ [isPercent]="data.rule.configuration.threshold.unit === '%'"
data.rule.configuration.threshold.min | percent: '1.2-2' [locale]="data.locale"
}}</label> [precision]="2"
} @else { [value]="data.rule.configuration.threshold.min"
<label>{{ data.rule.configuration.threshold.min }}</label> />
}
<mat-slider <mat-slider
class="flex-grow-1" class="flex-grow-1"
name="thresholdMax" name="thresholdMax"
@ -113,13 +115,12 @@
> >
<input matSliderThumb [(ngModel)]="data.settings.thresholdMax" /> <input matSliderThumb [(ngModel)]="data.settings.thresholdMax" />
</mat-slider> </mat-slider>
@if (data.rule.configuration.threshold.unit === '%') { <gf-value
<label>{{ [isPercent]="data.rule.configuration.threshold.unit === '%'"
data.rule.configuration.threshold.max | percent: '1.2-2' [locale]="data.locale"
}}</label> [precision]="2"
} @else { [value]="data.rule.configuration.threshold.max"
<label>{{ data.rule.configuration.threshold.max }}</label> />
}
</div> </div>
</div> </div>
} }

4
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts

@ -47,7 +47,7 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
styleUrls: ['./create-or-update-access-dialog.scss'], styleUrls: ['./create-or-update-access-dialog.scss'],
templateUrl: 'create-or-update-access-dialog.html' templateUrl: 'create-or-update-access-dialog.html'
}) })
export class GfCreateOrUpdateAccessDialog implements OnDestroy { export class GfCreateOrUpdateAccessDialogComponent implements OnDestroy {
public accessForm: FormGroup; public accessForm: FormGroup;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -55,7 +55,7 @@ export class GfCreateOrUpdateAccessDialog implements OnDestroy {
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA) private data: CreateOrUpdateAccessDialogParams, @Inject(MAT_DIALOG_DATA) private data: CreateOrUpdateAccessDialogParams,
public dialogRef: MatDialogRef<GfCreateOrUpdateAccessDialog>, public dialogRef: MatDialogRef<GfCreateOrUpdateAccessDialogComponent>,
private dataService: DataService, private dataService: DataService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private notificationService: NotificationService private notificationService: NotificationService

4
apps/client/src/app/components/user-account-access/user-account-access.component.ts

@ -30,7 +30,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { EMPTY, Subject } from 'rxjs'; import { EMPTY, Subject } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators'; import { catchError, takeUntil } from 'rxjs/operators';
import { GfCreateOrUpdateAccessDialog } from './create-or-update-access-dialog/create-or-update-access-dialog.component'; import { GfCreateOrUpdateAccessDialogComponent } from './create-or-update-access-dialog/create-or-update-access-dialog.component';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -179,7 +179,7 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit {
} }
private openCreateAccessDialog() { private openCreateAccessDialog() {
const dialogRef = this.dialog.open(GfCreateOrUpdateAccessDialog, { const dialogRef = this.dialog.open(GfCreateOrUpdateAccessDialogComponent, {
data: { data: {
access: { access: {
alias: '', alias: '',

5
apps/client/src/app/directives/file-drop/file-drop.directive.ts

@ -1,10 +1,9 @@
import { Directive, EventEmitter, HostListener, Output } from '@angular/core'; import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({ @Directive({
selector: '[gfFileDrop]', selector: '[gfFileDrop]'
standalone: false
}) })
export class FileDropDirective { export class GfFileDropDirective {
@Output() filesDropped = new EventEmitter<FileList>(); @Output() filesDropped = new EventEmitter<FileList>();
@HostListener('dragenter', ['$event']) onDragEnter(event: DragEvent) { @HostListener('dragenter', ['$event']) onDragEnter(event: DragEvent) {

9
apps/client/src/app/directives/file-drop/file-drop.module.ts

@ -1,9 +0,0 @@
import { NgModule } from '@angular/core';
import { FileDropDirective } from './file-drop.directive';
@NgModule({
declarations: [FileDropDirective],
exports: [FileDropDirective]
})
export class GfFileDropModule {}

8
apps/client/src/app/pages/home/home-page.routes.ts

@ -1,8 +1,8 @@
import { GfHomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component'; import { GfHomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component';
import { HomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
import { GfHomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component'; import { GfHomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component'; import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component';
import { HomeWatchlistComponent } from '@ghostfolio/client/components/home-watchlist/home-watchlist.component'; import { GfHomeWatchlistComponent } from '@ghostfolio/client/components/home-watchlist/home-watchlist.component';
import { MarketsComponent } from '@ghostfolio/client/components/markets/markets.component'; import { MarketsComponent } from '@ghostfolio/client/components/markets/markets.component';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { internalRoutes } from '@ghostfolio/common/routes/routes';
@ -31,7 +31,7 @@ export const routes: Routes = [
}, },
{ {
path: internalRoutes.home.subRoutes.markets.path, path: internalRoutes.home.subRoutes.markets.path,
component: HomeMarketComponent, component: GfHomeMarketComponent,
title: internalRoutes.home.subRoutes.markets.title title: internalRoutes.home.subRoutes.markets.title
}, },
{ {
@ -41,7 +41,7 @@ export const routes: Routes = [
}, },
{ {
path: internalRoutes.home.subRoutes.watchlist.path, path: internalRoutes.home.subRoutes.watchlist.path,
component: HomeWatchlistComponent, component: GfHomeWatchlistComponent,
title: internalRoutes.home.subRoutes.watchlist.title title: internalRoutes.home.subRoutes.watchlist.title
} }
], ],

4
apps/client/src/app/pages/markets/markets-page.component.ts

@ -1,4 +1,4 @@
import { HomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, OnDestroy } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
@ -6,7 +6,7 @@ import { Subject } from 'rxjs';
@Component({ @Component({
host: { class: 'page' }, host: { class: 'page' },
imports: [CommonModule, HomeMarketComponent], imports: [CommonModule, GfHomeMarketComponent],
selector: 'gf-markets-page', selector: 'gf-markets-page',
styleUrls: ['./markets-page.scss'], styleUrls: ['./markets-page.scss'],
templateUrl: './markets-page.html' templateUrl: './markets-page.html'

22
apps/client/src/app/pages/open/open-page-routing.module.ts

@ -1,22 +0,0 @@
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { OpenPageComponent } from './open-page.component';
const routes: Routes = [
{
canActivate: [AuthGuard],
component: OpenPageComponent,
path: '',
title: publicRoutes.openStartup.title
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class OpenPageRoutingModule {}

20
apps/client/src/app/pages/open/open-page.component.ts

@ -1,18 +1,28 @@
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Statistics, User } from '@ghostfolio/common/interfaces'; import { Statistics, User } from '@ghostfolio/common/interfaces';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
ChangeDetectorRef,
Component,
CUSTOM_ELEMENTS_SCHEMA,
OnDestroy,
OnInit
} from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
host: { class: 'page' }, host: { class: 'page' },
imports: [CommonModule, GfValueComponent, MatCardModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-open-page', selector: 'gf-open-page',
styleUrls: ['./open-page.scss'], styleUrls: ['./open-page.scss'],
templateUrl: './open-page.html', templateUrl: './open-page.html'
standalone: false
}) })
export class OpenPageComponent implements OnDestroy, OnInit { export class GfOpenPageComponent implements OnDestroy, OnInit {
public statistics: Statistics; public statistics: Statistics;
public user: User; public user: User;

20
apps/client/src/app/pages/open/open-page.module.ts

@ -1,20 +0,0 @@
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { OpenPageRoutingModule } from './open-page-routing.module';
import { OpenPageComponent } from './open-page.component';
@NgModule({
declarations: [OpenPageComponent],
imports: [
CommonModule,
GfValueComponent,
MatCardModule,
OpenPageRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class OpenPageModule {}

15
apps/client/src/app/pages/open/open-page.routes.ts

@ -0,0 +1,15 @@
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { Routes } from '@angular/router';
import { GfOpenPageComponent } from './open-page.component';
export const routes: Routes = [
{
canActivate: [AuthGuard],
component: GfOpenPageComponent,
path: '',
title: publicRoutes.openStartup.title
}
];

22
apps/client/src/app/pages/portfolio/activities/activities-page.component.ts

@ -27,8 +27,8 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { GfCreateOrUpdateActivityDialog } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component'; import { GfCreateOrUpdateActivityDialogComponent } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component';
import { GfImportActivitiesDialog } from './import-activities-dialog/import-activities-dialog.component'; import { GfImportActivitiesDialogComponent } from './import-activities-dialog/import-activities-dialog.component';
import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces';
@Component({ @Component({
@ -245,7 +245,7 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
} }
public onImport() { public onImport() {
const dialogRef = this.dialog.open(GfImportActivitiesDialog, { const dialogRef = this.dialog.open(GfImportActivitiesDialogComponent, {
data: { data: {
deviceType: this.deviceType, deviceType: this.deviceType,
user: this.user user: this.user
@ -268,7 +268,7 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
} }
public onImportDividends() { public onImportDividends() {
const dialogRef = this.dialog.open(GfImportActivitiesDialog, { const dialogRef = this.dialog.open(GfImportActivitiesDialogComponent, {
data: { data: {
activityTypes: ['DIVIDEND'], activityTypes: ['DIVIDEND'],
deviceType: this.deviceType, deviceType: this.deviceType,
@ -306,7 +306,9 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
} }
public openUpdateActivityDialog(aActivity: Activity) { public openUpdateActivityDialog(aActivity: Activity) {
const dialogRef = this.dialog.open(GfCreateOrUpdateActivityDialog, { const dialogRef = this.dialog.open(
GfCreateOrUpdateActivityDialogComponent,
{
data: { data: {
activity: aActivity, activity: aActivity,
accounts: this.user?.accounts, accounts: this.user?.accounts,
@ -314,7 +316,8 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); }
);
dialogRef dialogRef
.afterClosed() .afterClosed()
@ -347,7 +350,9 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
.subscribe((user) => { .subscribe((user) => {
this.updateUser(user); this.updateUser(user);
const dialogRef = this.dialog.open(GfCreateOrUpdateActivityDialog, { const dialogRef = this.dialog.open(
GfCreateOrUpdateActivityDialogComponent,
{
data: { data: {
accounts: this.user?.accounts, accounts: this.user?.accounts,
activity: { activity: {
@ -363,7 +368,8 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); }
);
dialogRef dialogRef
.afterClosed() .afterClosed()

4
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -76,7 +76,7 @@ import { ActivityType } from './types/activity-type.type';
styleUrls: ['./create-or-update-activity-dialog.scss'], styleUrls: ['./create-or-update-activity-dialog.scss'],
templateUrl: 'create-or-update-activity-dialog.html' templateUrl: 'create-or-update-activity-dialog.html'
}) })
export class GfCreateOrUpdateActivityDialog implements OnDestroy { export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
public activityForm: FormGroup; public activityForm: FormGroup;
public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass) public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass)
@ -110,7 +110,7 @@ export class GfCreateOrUpdateActivityDialog implements OnDestroy {
@Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams, @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams,
private dataService: DataService, private dataService: DataService,
private dateAdapter: DateAdapter<any>, private dateAdapter: DateAdapter<any>,
public dialogRef: MatDialogRef<GfCreateOrUpdateActivityDialog>, public dialogRef: MatDialogRef<GfCreateOrUpdateActivityDialogComponent>,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@Inject(MAT_DATE_LOCALE) private locale: string, @Inject(MAT_DATE_LOCALE) private locale: string,
private userService: UserService private userService: UserService

14
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -4,8 +4,8 @@ import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component'; import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component';
import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component'; import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component';
import { GfFileDropModule } from '@ghostfolio/client/directives/file-drop/file-drop.module'; import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service';
import { PortfolioPosition } from '@ghostfolio/common/interfaces'; import { PortfolioPosition } from '@ghostfolio/common/interfaces';
@ -15,6 +15,7 @@ import {
StepperOrientation, StepperOrientation,
StepperSelectionEvent StepperSelectionEvent
} from '@angular/cdk/stepper'; } from '@angular/cdk/stepper';
import { CommonModule } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
@ -59,11 +60,12 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column h-100' }, host: { class: 'd-flex flex-column h-100' },
imports: [ imports: [
CommonModule,
GfActivitiesTableComponent, GfActivitiesTableComponent,
GfDialogFooterComponent, GfDialogFooterComponent,
GfDialogHeaderComponent, GfDialogHeaderComponent,
GfFileDropModule, GfFileDropDirective,
GfSymbolModule, GfSymbolPipe,
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,
@ -78,7 +80,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces';
styleUrls: ['./import-activities-dialog.scss'], styleUrls: ['./import-activities-dialog.scss'],
templateUrl: 'import-activities-dialog.html' templateUrl: 'import-activities-dialog.html'
}) })
export class GfImportActivitiesDialog implements OnDestroy { export class GfImportActivitiesDialogComponent implements OnDestroy {
public accounts: CreateAccountWithBalancesDto[] = []; public accounts: CreateAccountWithBalancesDto[] = [];
public activities: Activity[] = []; public activities: Activity[] = [];
public assetProfileForm: FormGroup; public assetProfileForm: FormGroup;
@ -109,7 +111,7 @@ export class GfImportActivitiesDialog implements OnDestroy {
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
public dialogRef: MatDialogRef<GfImportActivitiesDialog>, public dialogRef: MatDialogRef<GfImportActivitiesDialogComponent>,
private importActivitiesService: ImportActivitiesService, private importActivitiesService: ImportActivitiesService,
private snackBar: MatSnackBar private snackBar: MatSnackBar
) { ) {

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

@ -1,7 +1,7 @@
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { User } from '@ghostfolio/common/interfaces'; import { FireWealth, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator'; import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
@ -31,7 +31,7 @@ import { takeUntil } from 'rxjs/operators';
}) })
export class GfFirePageComponent implements OnDestroy, OnInit { export class GfFirePageComponent implements OnDestroy, OnInit {
public deviceType: string; public deviceType: string;
public fireWealth: Big; public fireWealth: FireWealth;
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public isLoading = false; public isLoading = false;
@ -58,14 +58,20 @@ export class GfFirePageComponent implements OnDestroy, OnInit {
.fetchPortfolioDetails() .fetchPortfolioDetails()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ summary }) => { .subscribe(({ summary }) => {
this.fireWealth = summary.fireWealth this.fireWealth = {
? new Big(summary.fireWealth) today: {
: new Big(0); valueInBaseCurrency: summary.fireWealth
? summary.fireWealth.today.valueInBaseCurrency
: 0
}
};
if (this.user.subscription?.type === 'Basic') { if (this.user.subscription?.type === 'Basic') {
this.fireWealth = new Big(10000); this.fireWealth = {
today: {
valueInBaseCurrency: 10000
}
};
} }
this._calculateWithdrawalRates(); this._calculateWithdrawalRates();
this.isLoading = false; this.isLoading = false;

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

@ -14,7 +14,7 @@
[colorScheme]="user?.settings?.colorScheme" [colorScheme]="user?.settings?.colorScheme"
[currency]="user?.settings?.baseCurrency" [currency]="user?.settings?.baseCurrency"
[deviceType]="deviceType" [deviceType]="deviceType"
[fireWealth]="fireWealth?.toNumber()" [fireWealth]="fireWealth?.today.valueInBaseCurrency"
[hasPermissionToUpdateUserSettings]=" [hasPermissionToUpdateUserSettings]="
!hasImpersonationId && hasPermissionToUpdateUserSettings !hasImpersonationId && hasPermissionToUpdateUserSettings
" "
@ -100,7 +100,7 @@
[isCurrency]="true" [isCurrency]="true"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency" [unit]="user?.settings?.baseCurrency"
[value]="fireWealth?.toNumber()" [value]="fireWealth?.today.valueInBaseCurrency"
/> />
</span> </span>
<ng-container>&nbsp;</ng-container> <ng-container>&nbsp;</ng-container>

9
apps/client/src/app/pipes/symbol/symbol.module.ts

@ -1,9 +0,0 @@
import { NgModule } from '@angular/core';
import { SymbolPipe } from './symbol.pipe';
@NgModule({
declarations: [SymbolPipe],
exports: [SymbolPipe]
})
export class GfSymbolModule {}

5
apps/client/src/app/pipes/symbol/symbol.pipe.ts

@ -3,10 +3,9 @@ import { prettifySymbol } from '@ghostfolio/common/helper';
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ @Pipe({
name: 'gfSymbol', name: 'gfSymbol'
standalone: false
}) })
export class SymbolPipe implements PipeTransform { export class GfSymbolPipe implements PipeTransform {
public transform(aSymbol: string) { public transform(aSymbol: string) {
return prettifySymbol(aSymbol); return prettifySymbol(aSymbol);
} }

2
apps/client/src/locales/messages.de.xlf

@ -5393,7 +5393,7 @@
</trans-unit> </trans-unit>
<trans-unit id="3302046820145091217" datatype="html"> <trans-unit id="3302046820145091217" datatype="html">
<source>,</source> <source>,</source>
<target state="translated">entnehmen,</target> <target state="translated"> entnehmen,</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
<context context-type="linenumber">93</context> <context context-type="linenumber">93</context>

3
libs/common/src/lib/interfaces/fire-wealth.interface.ts

@ -0,0 +1,3 @@
export interface FireWealth {
today: { valueInBaseCurrency: number };
}

2
libs/common/src/lib/interfaces/index.ts

@ -19,6 +19,7 @@ import type { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'
import type { Export } from './export.interface'; import type { Export } from './export.interface';
import type { FilterGroup } from './filter-group.interface'; import type { FilterGroup } from './filter-group.interface';
import type { Filter } from './filter.interface'; import type { Filter } from './filter.interface';
import type { FireWealth } from './fire-wealth.interface';
import type { HistoricalDataItem } from './historical-data-item.interface'; import type { HistoricalDataItem } from './historical-data-item.interface';
import type { HoldingWithParents } from './holding-with-parents.interface'; import type { HoldingWithParents } from './holding-with-parents.interface';
import type { Holding } from './holding.interface'; import type { Holding } from './holding.interface';
@ -104,6 +105,7 @@ export {
Export, Export,
Filter, Filter,
FilterGroup, FilterGroup,
FireWealth,
HistoricalDataItem, HistoricalDataItem,
HistoricalResponse, HistoricalResponse,
Holding, Holding,

3
libs/common/src/lib/interfaces/portfolio-summary.interface.ts

@ -1,3 +1,4 @@
import { FireWealth } from './fire-wealth.interface';
import { PortfolioPerformance } from './portfolio-performance.interface'; import { PortfolioPerformance } from './portfolio-performance.interface';
export interface PortfolioSummary extends PortfolioPerformance { export interface PortfolioSummary extends PortfolioPerformance {
@ -16,7 +17,7 @@ export interface PortfolioSummary extends PortfolioPerformance {
fees: number; fees: number;
filteredValueInBaseCurrency?: number; filteredValueInBaseCurrency?: number;
filteredValueInPercentage?: number; filteredValueInPercentage?: number;
fireWealth: number; fireWealth: FireWealth;
grossPerformance: number; grossPerformance: number;
grossPerformanceWithCurrencyEffect: number; grossPerformanceWithCurrencyEffect: number;
interest: number; interest: number;

4
libs/ui/src/lib/activities-filter/activities-filter.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { Filter, FilterGroup } from '@ghostfolio/common/interfaces'; import { Filter, FilterGroup } from '@ghostfolio/common/interfaces';
import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { COMMA, ENTER } from '@angular/cdk/keycodes';
@ -39,7 +39,7 @@ import { translate } from '../i18n';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
CommonModule, CommonModule,
GfSymbolModule, GfSymbolPipe,
IonIcon, IonIcon,
MatAutocompleteModule, MatAutocompleteModule,
MatButtonModule, MatButtonModule,

4
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -1,7 +1,7 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { import {
DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE,
TAG_ID_EXCLUDE_FROM_ANALYSIS TAG_ID_EXCLUDE_FROM_ANALYSIS
@ -73,7 +73,7 @@ import { GfValueComponent } from '../value/value.component';
GfActivityTypeComponent, GfActivityTypeComponent,
GfEntityLogoComponent, GfEntityLogoComponent,
GfNoTransactionsInfoComponent, GfNoTransactionsInfoComponent,
GfSymbolModule, GfSymbolPipe,
GfValueComponent, GfValueComponent,
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,

4
libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { FocusableOption } from '@angular/cdk/a11y'; import { FocusableOption } from '@angular/cdk/a11y';
@ -24,7 +24,7 @@ import {
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [GfSymbolModule, RouterModule], imports: [GfSymbolPipe, RouterModule],
selector: 'gf-assistant-list-item', selector: 'gf-assistant-list-item',
styleUrls: ['./assistant-list-item.scss'], styleUrls: ['./assistant-list-item.scss'],
templateUrl: './assistant-list-item.html' templateUrl: './assistant-list-item.html'

4
libs/ui/src/lib/assistant/assistant.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
@ -76,7 +76,7 @@ import {
FormsModule, FormsModule,
GfAssistantListItemComponent, GfAssistantListItemComponent,
GfEntityLogoComponent, GfEntityLogoComponent,
GfSymbolModule, GfSymbolPipe,
IonIcon, IonIcon,
MatButtonModule, MatButtonModule,
MatFormFieldModule, MatFormFieldModule,

1
libs/ui/src/lib/fire-calculator/fire-calculator.component.html

@ -40,6 +40,7 @@
formControlName="retirementDate" formControlName="retirementDate"
matInput matInput
[matDatepicker]="datepicker" [matDatepicker]="datepicker"
[min]="minDate"
/> />
<mat-datepicker-toggle <mat-datepicker-toggle
matIconSuffix matIconSuffix

2
libs/ui/src/lib/fire-calculator/fire-calculator.component.ts

@ -44,6 +44,7 @@ import 'chartjs-adapter-date-fns';
import Color from 'color'; import Color from 'color';
import { import {
add, add,
addDays,
addYears, addYears,
getMonth, getMonth,
setMonth, setMonth,
@ -102,6 +103,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
}); });
public chart: Chart<'bar'>; public chart: Chart<'bar'>;
public isLoading = true; public isLoading = true;
public minDate = addDays(new Date(), 1);
public periodsToRetire = 0; public periodsToRetire = 0;
private readonly CONTRIBUTION_PERIOD = 12; private readonly CONTRIBUTION_PERIOD = 12;

4
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { getLocale } from '@ghostfolio/common/helper'; import { getLocale } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
@ -34,7 +34,7 @@ import { GfValueComponent } from '../value/value.component';
imports: [ imports: [
CommonModule, CommonModule,
GfEntityLogoComponent, GfEntityLogoComponent,
GfSymbolModule, GfSymbolPipe,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,

4
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { LookupItem } from '@ghostfolio/common/interfaces'; import { LookupItem } from '@ghostfolio/common/interfaces';
@ -57,7 +57,7 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
imports: [ imports: [
FormsModule, FormsModule,
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
GfSymbolModule, GfSymbolPipe,
MatAutocompleteModule, MatAutocompleteModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,

4
libs/ui/src/lib/top-holdings/top-holdings.component.ts

@ -1,4 +1,4 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { getLocale } from '@ghostfolio/common/helper'; import { getLocale } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
@ -46,7 +46,7 @@ import { GfValueComponent } from '../value/value.component';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
CommonModule, CommonModule,
GfSymbolModule, GfSymbolPipe,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,
MatPaginatorModule, MatPaginatorModule,

96
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.204.0", "version": "2.205.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.204.0", "version": "2.205.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -44,7 +44,7 @@
"@nestjs/schedule": "6.0.0", "@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3", "@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2", "@openrouter/ai-sdk-provider": "0.7.2",
"@prisma/client": "6.16.1", "@prisma/client": "6.16.3",
"@simplewebauthn/browser": "13.1.0", "@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1", "@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0", "@stripe/stripe-js": "7.9.0",
@ -149,7 +149,7 @@
"nx": "21.5.1", "nx": "21.5.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.16.1", "prisma": "6.16.3",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"replace-in-file": "8.3.0", "replace-in-file": "8.3.0",
@ -11960,9 +11960,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.3.tgz",
"integrity": "sha512-QaBCOY29lLAxEFFJgBPyW3WInCW52fJeQTmWx/h6YsP5u0bwuqP51aP0uhqFvhK9DaZPwvai/M4tSDYLVE9vRg==", "integrity": "sha512-JfNfAtXG+/lIopsvoZlZiH2k5yNx87mcTS4t9/S5oufM1nKdXYxOvpDC1XoTCFBa5cQh7uXnbMPsmZrwZY80xw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -11982,9 +11982,9 @@
} }
}, },
"node_modules/@prisma/config": { "node_modules/@prisma/config": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.3.tgz",
"integrity": "sha512-sz3uxRPNL62QrJ0EYiujCFkIGZ3hg+9hgC1Ae1HjoYuj0BxCqHua4JNijYvYCrh9LlofZDZcRBX3tHBfLvAngA==", "integrity": "sha512-VlsLnG4oOuKGGMToEeVaRhoTBZu5H3q51jTQXb/diRags3WV0+BQK5MolJTtP6G7COlzoXmWeS11rNBtvg+qFQ==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -11995,53 +11995,53 @@
} }
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.3.tgz",
"integrity": "sha512-RWv/VisW5vJE4cDRTuAHeVedtGoItXTnhuLHsSlJ9202QKz60uiXWywBlVcqXVq8bFeIZoCoWH+R1duZJPwqLw==", "integrity": "sha512-89DdqWtdKd7qoc9/qJCKLTazj3W3zPEiz0hc7HfZdpjzm21c7orOUB5oHWJsG+4KbV4cWU5pefq3CuDVYF9vgA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.3.tgz",
"integrity": "sha512-EOnEM5HlosPudBqbI+jipmaW/vQEaF0bKBo4gVkGabasINHR6RpC6h44fKZEqx4GD8CvH+einD2+b49DQrwrAg==", "integrity": "sha512-b+Rl4nzQDcoqe6RIpSHv8f5lLnwdDGvXhHjGDiokObguAAv/O1KaX1Oc69mBW/GFWKQpCkOraobLjU6s1h8HGg==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.16.1", "@prisma/debug": "6.16.3",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/engines-version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a",
"@prisma/fetch-engine": "6.16.1", "@prisma/fetch-engine": "6.16.3",
"@prisma/get-platform": "6.16.1" "@prisma/get-platform": "6.16.3"
} }
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a.tgz",
"integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==", "integrity": "sha512-fftRmosBex48Ph1v2ll1FrPpirwtPZpNkE5CDCY1Lw2SD2ctyrLlVlHiuxDAAlALwWBOkPbAll4+EaqdGuMhJw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.3.tgz",
"integrity": "sha512-fl/PKQ8da5YTayw86WD3O9OmKJEM43gD3vANy2hS5S1CnfW2oPXk+Q03+gUWqcKK306QqhjjIHRFuTZ31WaosQ==", "integrity": "sha512-bUoRIkVaI+CCaVGrSfcKev0/Mk4ateubqWqGZvQ9uCqFv2ENwWIR3OeNuGin96nZn5+SkebcD7RGgKr/+mJelw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.16.1", "@prisma/debug": "6.16.3",
"@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", "@prisma/engines-version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a",
"@prisma/get-platform": "6.16.1" "@prisma/get-platform": "6.16.3"
} }
}, },
"node_modules/@prisma/get-platform": { "node_modules/@prisma/get-platform": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.1.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.3.tgz",
"integrity": "sha512-kUfg4vagBG7dnaGRcGd1c0ytQFcDj2SUABiuveIpL3bthFdTLI6PJeLEia6Q8Dgh+WhPdo0N2q0Fzjk63XTyaA==", "integrity": "sha512-X1LxiFXinJ4iQehrodGp0f66Dv6cDL0GbRlcCoLtSu6f4Wi+hgo7eND/afIs5029GQLgNWKZ46vn8hjyXTsHLA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "6.16.1" "@prisma/debug": "6.16.3"
} }
}, },
"node_modules/@redis/client": { "node_modules/@redis/client": {
@ -17285,9 +17285,9 @@
} }
}, },
"node_modules/c12/node_modules/jiti": { "node_modules/c12/node_modules/jiti": {
"version": "2.5.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -33713,16 +33713,16 @@
} }
}, },
"node_modules/nypm": { "node_modules/nypm": {
"version": "0.6.1", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.1.tgz", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
"integrity": "sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==", "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"citty": "^0.1.6", "citty": "^0.1.6",
"consola": "^3.4.2", "consola": "^3.4.2",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"pkg-types": "^2.2.0", "pkg-types": "^2.3.0",
"tinyexec": "^1.0.1" "tinyexec": "^1.0.1"
}, },
"bin": { "bin": {
@ -34903,9 +34903,9 @@
} }
}, },
"node_modules/pkg-types": { "node_modules/pkg-types": {
"version": "2.2.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -35747,15 +35747,15 @@
} }
}, },
"node_modules/prisma": { "node_modules/prisma": {
"version": "6.16.1", "version": "6.16.3",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.1.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.3.tgz",
"integrity": "sha512-MFkMU0eaDDKAT4R/By2IA9oQmwLTxokqv2wegAErr9Rf+oIe7W2sYpE/Uxq0H2DliIR7vnV63PkC1bEwUtl98w==", "integrity": "sha512-4tJq3KB9WRshH5+QmzOLV54YMkNlKOtLKaSdvraI5kC/axF47HuOw6zDM8xrxJ6s9o2WodY654On4XKkrobQdQ==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/config": "6.16.1", "@prisma/config": "6.16.3",
"@prisma/engines": "6.16.1" "@prisma/engines": "6.16.3"
}, },
"bin": { "bin": {
"prisma": "build/index.js" "prisma": "build/index.js"

6
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.204.0", "version": "2.205.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",
@ -90,7 +90,7 @@
"@nestjs/schedule": "6.0.0", "@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3", "@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2", "@openrouter/ai-sdk-provider": "0.7.2",
"@prisma/client": "6.16.1", "@prisma/client": "6.16.3",
"@simplewebauthn/browser": "13.1.0", "@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1", "@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0", "@stripe/stripe-js": "7.9.0",
@ -195,7 +195,7 @@
"nx": "21.5.1", "nx": "21.5.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.16.1", "prisma": "6.16.3",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"replace-in-file": "8.3.0", "replace-in-file": "8.3.0",

Loading…
Cancel
Save