- |
-
- |
+ |
@if (element.error) {
diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.html b/libs/ui/src/lib/fire-calculator/fire-calculator.component.html
index ef4722255..4f9ac456c 100644
--- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.html
+++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.html
@@ -52,8 +52,7 @@
startView="multi-year"
[disabled]="hasPermissionToUpdateUserSettings !== true"
(monthSelected)="setMonthAndYear($event, datepicker)"
- >
-
+ />
diff --git a/libs/ui/src/lib/value/value.component.html b/libs/ui/src/lib/value/value.component.html
index 1f61f30e5..14080c16d 100644
--- a/libs/ui/src/lib/value/value.component.html
+++ b/libs/ui/src/lib/value/value.component.html
@@ -4,7 +4,7 @@
}
-
+
@if (value || value === 0 || value === null) {
-
+
@if (subLabel) {
{{ subLabel }}
}
} @else {
-
+
}
From 7761c764b51ed95ce58296b885e54c9e2e704859 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?=
Date: Fri, 10 Oct 2025 20:59:38 +0200
Subject: [PATCH 02/32] Bugfix/fix server startup message to properly display
IPv6 addresses (#5716)
* Fix server startup message to properly display IPv6 addresses
* Update changelog
---
CHANGELOG.md | 4 ++++
apps/api/src/main.ts | 16 +++++++++++++++-
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6f9a053ff..197bcdac5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactored various components to use self-closing tags
+### Fixed
+
+- Fixed the server startup message to properly display IPv6 addresses
+
## 2.207.0 - 2025-10-08
### Added
diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts
index 41f156cbf..a8de3dc5e 100644
--- a/apps/api/src/main.ts
+++ b/apps/api/src/main.ts
@@ -90,7 +90,21 @@ async function bootstrap() {
await app.listen(PORT, HOST, () => {
logLogo();
- Logger.log(`Listening at http://${HOST}:${PORT}`);
+
+ let address = app.getHttpServer().address();
+
+ if (typeof address === 'object') {
+ const addressObject = address;
+ let host = addressObject.address;
+
+ if (addressObject.family === 'IPv6') {
+ host = `[${addressObject.address}]`;
+ }
+
+ address = `${host}:${addressObject.port}`;
+ }
+
+ Logger.log(`Listening at http://${address}`);
Logger.log('');
});
}
From 7642d823180c34511d98d5a9a927fbe353b304ad Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Fri, 10 Oct 2025 21:10:20 +0200
Subject: [PATCH 03/32] Bugfix/add missing type of access id in get public
portfolio endpoint (#5690)
* Add missing type
---
apps/api/src/app/endpoints/public/public.controller.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts
index 0f3ba4682..b09ced4fb 100644
--- a/apps/api/src/app/endpoints/public/public.controller.ts
+++ b/apps/api/src/app/endpoints/public/public.controller.ts
@@ -40,7 +40,7 @@ export class PublicController {
@UseInterceptors(RedactValuesInResponseInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPublicPortfolio(
- @Param('accessId') accessId
+ @Param('accessId') accessId: string
): Promise {
const access = await this.accessService.access({ id: accessId });
From b168a9e3c1b26f34a254b841c1aa1184dd0be61d Mon Sep 17 00:00:00 2001
From: Ayush Jain <136119112+URAYUSHJAIN@users.noreply.github.com>
Date: Sat, 11 Oct 2025 14:19:05 +0530
Subject: [PATCH 04/32] Feature/create infinite logo carousel component (#5671)
* Create infinite logo carousel component
* Update changelog
---
CHANGELOG.md | 1 +
.../pages/landing/landing-page.component.ts | 2 +
.../src/app/pages/landing/landing-page.html | 105 +--------
.../src/app/pages/landing/landing-page.scss | 76 +------
libs/ui/src/lib/logo-carousel/index.ts | 1 +
.../logo-carousel/interfaces/interfaces.ts | 7 +
.../logo-carousel.component.html | 16 ++
.../logo-carousel.component.scss | 213 ++++++++++++++++++
.../logo-carousel.component.stories.ts | 13 ++
.../logo-carousel/logo-carousel.component.ts | 110 +++++++++
10 files changed, 366 insertions(+), 178 deletions(-)
create mode 100644 libs/ui/src/lib/logo-carousel/index.ts
create mode 100644 libs/ui/src/lib/logo-carousel/interfaces/interfaces.ts
create mode 100644 libs/ui/src/lib/logo-carousel/logo-carousel.component.html
create mode 100644 libs/ui/src/lib/logo-carousel/logo-carousel.component.scss
create mode 100644 libs/ui/src/lib/logo-carousel/logo-carousel.component.stories.ts
create mode 100644 libs/ui/src/lib/logo-carousel/logo-carousel.component.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 197bcdac5..544a7bdc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- Changed the _As seen in_ section on the landing page to an animated carousel
- Refactored various components to use self-closing tags
### Fixed
diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts
index f16a9c14a..f755494e0 100644
--- a/apps/client/src/app/pages/landing/landing-page.component.ts
+++ b/apps/client/src/app/pages/landing/landing-page.component.ts
@@ -4,6 +4,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfCarouselComponent } from '@ghostfolio/ui/carousel';
import { GfLogoComponent } from '@ghostfolio/ui/logo';
+import { GfLogoCarouselComponent } from '@ghostfolio/ui/logo-carousel';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart';
@@ -27,6 +28,7 @@ import { Subject } from 'rxjs';
imports: [
CommonModule,
GfCarouselComponent,
+ GfLogoCarouselComponent,
GfLogoComponent,
GfValueComponent,
GfWorldMapChartComponent,
diff --git a/apps/client/src/app/pages/landing/landing-page.html b/apps/client/src/app/pages/landing/landing-page.html
index 7202e2910..7f77c31a7 100644
--- a/apps/client/src/app/pages/landing/landing-page.html
+++ b/apps/client/src/app/pages/landing/landing-page.html
@@ -114,109 +114,8 @@
As seen in
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/client/src/app/pages/landing/landing-page.scss b/apps/client/src/app/pages/landing/landing-page.scss
index 4c0c14efd..35f362b0f 100644
--- a/apps/client/src/app/pages/landing/landing-page.scss
+++ b/apps/client/src/app/pages/landing/landing-page.scss
@@ -34,69 +34,6 @@
&.logo-agplv3 {
mask-image: url('/assets/images/logo-AGPLv3.svg');
}
-
- &.logo-alternative-to {
- mask-image: url('/assets/images/logo-alternative-to.svg');
- }
-
- &.logo-awesome {
- background-image: url('/assets/images/logo-awesome.png');
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
- filter: grayscale(1);
- }
-
- &.logo-dev-community {
- mask-image: url('/assets/images/logo-dev-community.svg');
- }
-
- &.logo-hacker-news {
- mask-image: url('/assets/images/logo-hacker-news.svg');
- }
-
- &.logo-openalternative {
- mask-image: url('/assets/images/logo-openalternative.svg');
- }
-
- &.logo-privacy-tools {
- mask-image: url('/assets/images/logo-privacy-tools.svg');
- }
-
- &.logo-product-hunt {
- background-image: url('/assets/images/logo-product-hunt.png');
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
- filter: grayscale(1);
- }
-
- &.logo-reddit {
- mask-image: url('/assets/images/logo-reddit.svg');
- max-height: 1rem;
- }
-
- &.logo-sackgeld {
- mask-image: url('/assets/images/logo-sackgeld.png');
- }
-
- &.logo-selfh-st {
- mask-image: url('/assets/images/logo-selfh-st.svg');
- max-height: 1.25rem;
- }
-
- &.logo-sourceforge {
- mask-image: url('/assets/images/logo-sourceforge.svg');
- }
-
- &.logo-umbrel {
- mask-image: url('/assets/images/logo-umbrel.svg');
- max-height: 1.5rem;
- }
-
- &.logo-unraid {
- mask-image: url('/assets/images/logo-unraid.svg');
- }
}
.outro-inner-container {
@@ -128,18 +65,7 @@
}
.logo {
- &.logo-agplv3,
- &.logo-alternative-to,
- &.logo-dev-community,
- &.logo-hacker-news,
- &.logo-openalternative,
- &.logo-privacy-tools,
- &.logo-reddit,
- &.logo-sackgeld,
- &.logo-selfh-st,
- &.logo-sourceforge,
- &.logo-umbrel,
- &.logo-unraid {
+ &.logo-agplv3 {
background-color: rgba(var(--light-primary-text));
}
}
diff --git a/libs/ui/src/lib/logo-carousel/index.ts b/libs/ui/src/lib/logo-carousel/index.ts
new file mode 100644
index 000000000..e69f6039c
--- /dev/null
+++ b/libs/ui/src/lib/logo-carousel/index.ts
@@ -0,0 +1 @@
+export * from './logo-carousel.component';
diff --git a/libs/ui/src/lib/logo-carousel/interfaces/interfaces.ts b/libs/ui/src/lib/logo-carousel/interfaces/interfaces.ts
new file mode 100644
index 000000000..43dcfeefd
--- /dev/null
+++ b/libs/ui/src/lib/logo-carousel/interfaces/interfaces.ts
@@ -0,0 +1,7 @@
+export interface LogoItem {
+ className: string;
+ isMask?: boolean;
+ name: string;
+ title: string;
+ url: string;
+}
diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.html b/libs/ui/src/lib/logo-carousel/logo-carousel.component.html
new file mode 100644
index 000000000..79b4ca11b
--- /dev/null
+++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.html
@@ -0,0 +1,16 @@
+
+
+ @for (logo of logosRepeated; track $index) {
+
+ }
+
+
diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss
new file mode 100644
index 000000000..d8a8865f7
--- /dev/null
+++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss
@@ -0,0 +1,213 @@
+:host {
+ display: block;
+ overflow: hidden;
+ position: relative;
+ width: 100%;
+
+ .logo-carousel-container {
+ &::before,
+ &::after {
+ content: '';
+ height: 100%;
+ pointer-events: none;
+ position: absolute;
+ top: 0;
+ width: 100px;
+ z-index: 2;
+ }
+
+ &::before {
+ background: linear-gradient(
+ to right,
+ var(--light-background) 0%,
+ rgba(var(--palette-background-background), 0) 100%
+ );
+ left: 0;
+ }
+
+ &::after {
+ background: linear-gradient(
+ to left,
+ var(--light-background) 0%,
+ rgba(var(--palette-background-background), 0) 100%
+ );
+ right: 0;
+ }
+
+ @media (max-width: 768px) {
+ &::before,
+ &::after {
+ width: 50px;
+ }
+ }
+
+ @media (max-width: 576px) {
+ &::before,
+ &::after {
+ width: 30px;
+ }
+ }
+
+ .logo-carousel-track {
+ animation: scroll 60s linear infinite;
+ width: fit-content;
+
+ &:hover {
+ animation-play-state: paused;
+ }
+
+ .logo-carousel-item {
+ flex-shrink: 0;
+ min-width: 200px;
+ padding: 0 2rem;
+
+ @media (max-width: 768px) {
+ min-width: 150px;
+ padding: 0 1.5rem;
+ }
+
+ @media (max-width: 576px) {
+ min-width: 120px;
+ padding: 0 1rem;
+ }
+
+ .logo {
+ height: 3rem;
+ transition:
+ opacity 0.3s ease,
+ transform 0.3s ease;
+ width: 7.5rem;
+
+ &:hover {
+ opacity: 0.8;
+ }
+
+ &.mask {
+ background-color: rgba(var(--dark-secondary-text));
+ mask-position: center;
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ }
+
+ &.logo-alternative-to {
+ mask-image: url('/assets/images/logo-alternative-to.svg');
+ }
+
+ &.logo-awesome {
+ background-image: url('/assets/images/logo-awesome.png');
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: contain;
+ filter: grayscale(1);
+ }
+
+ &.logo-dev-community {
+ mask-image: url('/assets/images/logo-dev-community.svg');
+ }
+
+ &.logo-hacker-news {
+ mask-image: url('/assets/images/logo-hacker-news.svg');
+ }
+
+ &.logo-openalternative {
+ mask-image: url('/assets/images/logo-openalternative.svg');
+ }
+
+ &.logo-privacy-tools {
+ mask-image: url('/assets/images/logo-privacy-tools.svg');
+ }
+
+ &.logo-product-hunt {
+ background-image: url('/assets/images/logo-product-hunt.png');
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: contain;
+ filter: grayscale(1);
+ }
+
+ &.logo-reddit {
+ mask-image: url('/assets/images/logo-reddit.svg');
+ max-height: 1rem;
+ }
+
+ &.logo-sackgeld {
+ mask-image: url('/assets/images/logo-sackgeld.png');
+ }
+
+ &.logo-selfh-st {
+ mask-image: url('/assets/images/logo-selfh-st.svg');
+ max-height: 1.25rem;
+ }
+
+ &.logo-sourceforge {
+ mask-image: url('/assets/images/logo-sourceforge.svg');
+ }
+
+ &.logo-umbrel {
+ mask-image: url('/assets/images/logo-umbrel.svg');
+ max-height: 1.5rem;
+ }
+
+ &.logo-unraid {
+ mask-image: url('/assets/images/logo-unraid.svg');
+ }
+
+ @media (max-width: 768px) {
+ height: 2.5rem;
+ width: 6rem;
+ }
+
+ @media (max-width: 576px) {
+ height: 2rem;
+ width: 5rem;
+ }
+ }
+ }
+
+ @keyframes scroll {
+ 0% {
+ transform: translateX(0);
+ }
+ 100% {
+ transform: translateX(-50%);
+ }
+ }
+ }
+ }
+}
+
+:host-context(.theme-dark) {
+ .logo-carousel-container {
+ &::before {
+ background: linear-gradient(
+ to right,
+ var(--dark-background) 0%,
+ rgba(var(--palette-background-background-dark), 0) 100%
+ );
+ }
+
+ &::after {
+ background: linear-gradient(
+ to left,
+ var(--dark-background) 0%,
+ rgba(var(--palette-background-background-dark), 0) 100%
+ );
+ }
+
+ .logo {
+ &.logo-alternative-to,
+ &.logo-dev-community,
+ &.logo-hacker-news,
+ &.logo-openalternative,
+ &.logo-privacy-tools,
+ &.logo-reddit,
+ &.logo-sackgeld,
+ &.logo-selfh-st,
+ &.logo-sourceforge,
+ &.logo-umbrel,
+ &.logo-unraid {
+ background-color: rgba(var(--light-primary-text));
+ }
+ }
+ }
+}
diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.stories.ts b/libs/ui/src/lib/logo-carousel/logo-carousel.component.stories.ts
new file mode 100644
index 000000000..a4e88df98
--- /dev/null
+++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.stories.ts
@@ -0,0 +1,13 @@
+import type { Meta, StoryObj } from '@storybook/angular';
+
+import { GfLogoCarouselComponent } from './logo-carousel.component';
+
+const meta: Meta = {
+ title: 'Logo Carousel',
+ component: GfLogoCarouselComponent
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {};
diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts b/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts
new file mode 100644
index 000000000..d7d3fa6af
--- /dev/null
+++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.ts
@@ -0,0 +1,110 @@
+import { CommonModule } from '@angular/common';
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+
+import { LogoItem } from './interfaces/interfaces';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [CommonModule],
+ selector: 'gf-logo-carousel',
+ styleUrls: ['./logo-carousel.component.scss'],
+ templateUrl: './logo-carousel.component.html'
+})
+export class GfLogoCarouselComponent {
+ public readonly logos: LogoItem[] = [
+ {
+ className: 'logo-alternative-to',
+ isMask: true,
+ name: 'AlternativeTo',
+ title: 'AlternativeTo - Crowdsourced software recommendations',
+ url: 'https://alternativeto.net'
+ },
+ {
+ className: 'logo-awesome',
+ name: 'Awesome Selfhosted',
+ title:
+ 'Awesome-Selfhosted: A list of Free Software network services and web applications which can be hosted on your own servers',
+ url: 'https://github.com/awesome-selfhosted/awesome-selfhosted'
+ },
+ {
+ className: 'logo-dev-community',
+ isMask: true,
+ name: 'DEV Community',
+ title:
+ 'DEV Community - A constructive and inclusive social network for software developers',
+ url: 'https://dev.to'
+ },
+ {
+ className: 'logo-hacker-news',
+ isMask: true,
+ name: 'Hacker News',
+ title: 'Hacker News',
+ url: 'https://news.ycombinator.com'
+ },
+ {
+ className: 'logo-openalternative',
+ isMask: true,
+ name: 'OpenAlternative',
+ title: 'OpenAlternative: Open Source Alternatives to Popular Software',
+ url: 'https://openalternative.co'
+ },
+ {
+ className: 'logo-privacy-tools',
+ isMask: true,
+ name: 'Privacy Tools',
+ title: 'Privacy Tools: Software Alternatives and Encryption',
+ url: 'https://www.privacytools.io'
+ },
+ {
+ className: 'logo-product-hunt',
+ name: 'Product Hunt',
+ title: 'Product Hunt – The best new products in tech.',
+ url: 'https://www.producthunt.com'
+ },
+ {
+ className: 'logo-reddit',
+ isMask: true,
+ name: 'Reddit',
+ title: 'Reddit - Dive into anything',
+ url: 'https://www.reddit.com'
+ },
+ {
+ className: 'logo-sackgeld',
+ isMask: true,
+ name: 'Sackgeld',
+ title: 'Sackgeld.com – Apps für ein höheres Sackgeld',
+ url: 'https://www.sackgeld.com'
+ },
+ {
+ className: 'logo-selfh-st',
+ isMask: true,
+ name: 'selfh.st',
+ title: 'selfh.st — Self-hosted content and software',
+ url: 'https://selfh.st'
+ },
+ {
+ className: 'logo-sourceforge',
+ isMask: true,
+ name: 'SourceForge',
+ title:
+ 'SourceForge: The Complete Open-Source and Business Software Platform',
+ url: 'https://sourceforge.net'
+ },
+ {
+ className: 'logo-umbrel',
+ isMask: true,
+ name: 'Umbrel',
+ title: 'Umbrel — A personal server OS for self-hosting',
+ url: 'https://umbrel.com'
+ },
+ {
+ className: 'logo-unraid',
+ isMask: true,
+ name: 'Unraid',
+ title: 'Unraid | Unleash Your Hardware',
+ url: 'https://unraid.net'
+ }
+ ];
+
+ public readonly logosRepeated = [...this.logos, ...this.logos];
+}
From 94e8a7c6bc0b7050a1a6f65af6b14d1197ae01f2 Mon Sep 17 00:00:00 2001
From: Shivansh Pandey <131197200+Shivansh-22866@users.noreply.github.com>
Date: Sat, 11 Oct 2025 14:25:37 +0530
Subject: [PATCH 05/32] Feature/add support for configuring safe withdrawal
rate (#5679)
* Add support for configuring safe withdrawal rate
* Update changelog
---
CHANGELOG.md | 4 ++
.../portfolio/fire/fire-page.component.ts | 59 ++++++++++++++++---
.../app/pages/portfolio/fire/fire-page.html | 35 ++++++++---
.../app/pages/portfolio/fire/fire-page.scss | 16 +++++
4 files changed, 98 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 544a7bdc6..58a7b3b48 100644
--- a/CHANGELOG.md
+++ b/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
diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
index c80b55c45..63187c05c 100644
--- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
+++ b/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(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);
+ }
+ }
}
diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html
index d6548f761..ce51717fa 100644
--- a/apps/client/src/app/pages/portfolio/fire/fire-page.html
+++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html
@@ -105,15 +105,32 @@
and a safe withdrawal rate (SWR) of
-
- .
+ @if (
+ !hasImpersonationId &&
+ hasPermissionToUpdateUserSettings &&
+ user?.settings?.isExperimentalFeatures
+ ) {
+ .
+ } @else {
+
+ .
+ }
}
diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.scss b/apps/client/src/app/pages/portfolio/fire/fire-page.scss
index 5d4e87f30..2892885c9 100644
--- a/apps/client/src/app/pages/portfolio/fire/fire-page.scss
+++ b/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));
+ }
}
From ab7f1fd88141ecaa868dc14899fadba4701b5616 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 11 Oct 2025 11:25:28 +0200
Subject: [PATCH 06/32] Task/set up GitHub Sponsors (#5723)
* Add github
---
.github/FUNDING.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 9df3e0d6d..a881e8fc8 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
buy_me_a_coffee: ghostfolio
+github: ghostfolio
From 948233c6510e4d53c304e9761d2c438669a4a22c Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 11 Oct 2025 11:26:10 +0200
Subject: [PATCH 07/32] Task/remove @IsOptional() from dataSource in
CreateOrderDto (#5703)
* Remove is @IsOptional() from dataSource
---
apps/api/src/app/order/create-order.dto.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts
index af87fd93e..ba7a1d868 100644
--- a/apps/api/src/app/order/create-order.dto.ts
+++ b/apps/api/src/app/order/create-order.dto.ts
@@ -44,8 +44,7 @@ export class CreateOrderDto {
customCurrency?: string;
@IsEnum(DataSource)
- @IsOptional()
- dataSource?: DataSource;
+ dataSource: DataSource;
@IsISO8601()
@Validate(IsAfter1970Constraint)
From c5c11929008ac5bf6c4e56bcf9fe337f3a99d4f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sven=20G=C3=BCnther?=
Date: Sat, 11 Oct 2025 17:07:42 +0200
Subject: [PATCH 08/32] Bugfix/import of custom asset profiles (#5670)
* Import of custom asset profiles
* Update changelog
---
CHANGELOG.md | 1 +
apps/api/src/app/import/import.service.ts | 38 ++++++++++++++++
test/import/ok/penthouse-apartment.json | 53 +++++++++++++++++++++++
3 files changed, 92 insertions(+)
create mode 100644 test/import/ok/penthouse-apartment.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58a7b3b48..c7472e421 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed the server startup message to properly display IPv6 addresses
+- Fixed an issue where importing custom asset profiles failed due to validation errors
## 2.207.0 - 2025-10-08
diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts
index 82231d237..69ec781c3 100644
--- a/apps/api/src/app/import/import.service.ts
+++ b/apps/api/src/app/import/import.service.ts
@@ -373,6 +373,7 @@ export class ImportService {
const assetProfiles = await this.validateActivities({
activitiesDto,
+ assetProfilesWithMarketDataDto,
maxActivitiesToImport,
user
});
@@ -698,10 +699,12 @@ export class ImportService {
private async validateActivities({
activitiesDto,
+ assetProfilesWithMarketDataDto,
maxActivitiesToImport,
user
}: {
activitiesDto: Partial[];
+ assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles'];
maxActivitiesToImport: number;
user: UserWithSettings;
}) {
@@ -749,6 +752,41 @@ export class ImportService {
)?.[symbol]
};
+ if (!assetProfile?.name) {
+ const assetProfileInImport = assetProfilesWithMarketDataDto?.find(
+ (profile) => {
+ return (
+ profile.dataSource === dataSource && profile.symbol === symbol
+ );
+ }
+ );
+
+ if (assetProfileInImport) {
+ // Merge all fields of custom asset profiles into the validation object
+ Object.assign(assetProfile, {
+ assetClass: assetProfileInImport.assetClass,
+ assetSubClass: assetProfileInImport.assetSubClass,
+ comment: assetProfileInImport.comment,
+ countries: assetProfileInImport.countries,
+ currency: assetProfileInImport.currency,
+ cusip: assetProfileInImport.cusip,
+ dataSource: assetProfileInImport.dataSource,
+ figi: assetProfileInImport.figi,
+ figiComposite: assetProfileInImport.figiComposite,
+ figiShareClass: assetProfileInImport.figiShareClass,
+ holdings: assetProfileInImport.holdings,
+ isActive: assetProfileInImport.isActive,
+ isin: assetProfileInImport.isin,
+ name: assetProfileInImport.name,
+ scraperConfiguration: assetProfileInImport.scraperConfiguration,
+ sectors: assetProfileInImport.sectors,
+ symbol: assetProfileInImport.symbol,
+ symbolMapping: assetProfileInImport.symbolMapping,
+ url: assetProfileInImport.url
+ });
+ }
+ }
+
if (
(dataSource !== 'MANUAL' && type === 'BUY') ||
type === 'DIVIDEND' ||
diff --git a/test/import/ok/penthouse-apartment.json b/test/import/ok/penthouse-apartment.json
new file mode 100644
index 000000000..3b230cf76
--- /dev/null
+++ b/test/import/ok/penthouse-apartment.json
@@ -0,0 +1,53 @@
+{
+ "meta": {
+ "date": "2023-02-05T00:00:00.000Z",
+ "version": "dev"
+ },
+ "accounts": [],
+ "assetProfiles": [
+ {
+ "assetClass": null,
+ "assetSubClass": null,
+ "comment": null,
+ "countries": [],
+ "currency": "USD",
+ "cusip": null,
+ "dataSource": "MANUAL",
+ "figi": null,
+ "figiComposite": null,
+ "figiShareClass": null,
+ "holdings": [],
+ "isActive": true,
+ "isin": null,
+ "marketData": [],
+ "name": "Penthouse Apartment",
+ "scraperConfiguration": null,
+ "sectors": [],
+ "symbol": "7e91b7d4-1430-4212-8380-289a06c9bbc1",
+ "symbolMapping": {},
+ "url": null
+ }
+ ],
+ "platforms": [],
+ "tags": [],
+ "activities": [
+ {
+ "accountId": null,
+ "comment": null,
+ "currency": "USD",
+ "dataSource": "MANUAL",
+ "date": "2022-01-01T00:00:00.000Z",
+ "fee": 0,
+ "quantity": 1,
+ "symbol": "7e91b7d4-1430-4212-8380-289a06c9bbc1",
+ "tags": [],
+ "type": "BUY",
+ "unitPrice": 500000,
+ }
+ ],
+ "user": {
+ "settings": {
+ "currency": "USD"
+ }
+ }
+}
From 13838bed6d20307eccf3ea02cb632ea0e5642b5c Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 11 Oct 2025 20:12:37 +0200
Subject: [PATCH 09/32] Feature/improve language localization for de 20250811
(#5730)
* Update translation
* Update changelog
---
CHANGELOG.md | 1 +
apps/client/src/locales/messages.de.xlf | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7472e421..1128a6205 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changed the _As seen in_ section on the landing page to an animated carousel
- Refactored various components to use self-closing tags
+- Improved the language localization for German (`de`)
### Fixed
diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf
index 3e12ef852..9211617f0 100644
--- a/apps/client/src/locales/messages.de.xlf
+++ b/apps/client/src/locales/messages.de.xlf
@@ -5409,7 +5409,7 @@
,
- entnehmen,
+ entnehmen,
apps/client/src/app/pages/portfolio/fire/fire-page.html
93
From d2fe16c794edc91334b8c58ecb492705b59ef694 Mon Sep 17 00:00:00 2001
From: Tanbir Ali <124070023+tanbirali@users.noreply.github.com>
Date: Sat, 11 Oct 2025 23:44:25 +0530
Subject: [PATCH 10/32] Task/refactor transactionCount to activitiesCount in
portfolio holding response (#5709)
* Refactor transactionCount to activitiesCount in portfolio holding response
* Update changelog
---
CHANGELOG.md | 1 +
apps/api/src/app/portfolio/portfolio.service.ts | 6 +++---
.../holding-detail-dialog.component.ts | 8 +++-----
.../holding-detail-dialog/holding-detail-dialog.html | 6 +++---
.../responses/portfolio-holding-response.interface.ts | 2 +-
5 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1128a6205..d178ae97a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Changed the _As seen in_ section on the landing page to an animated carousel
+- Refactored `transactionCount` to `activitiesCount` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol`
- Refactored various components to use self-closing tags
- Improved the language localization for German (`de`)
diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts
index e73f79784..f774378ef 100644
--- a/apps/api/src/app/portfolio/portfolio.service.ts
+++ b/apps/api/src/app/portfolio/portfolio.service.ts
@@ -778,6 +778,7 @@ export class PortfolioService {
if (activities.length === 0) {
return {
activities: [],
+ activitiesCount: 0,
averagePrice: undefined,
dataProviderInfo: undefined,
dividendInBaseCurrency: undefined,
@@ -802,7 +803,6 @@ export class PortfolioService {
quantity: undefined,
SymbolProfile: undefined,
tags: [],
- transactionCount: undefined,
value: undefined
};
}
@@ -966,8 +966,8 @@ export class PortfolioService {
marketPriceMin,
SymbolProfile,
tags,
- transactionCount,
activities: activitiesOfHolding,
+ activitiesCount: transactionCount,
averagePrice: averagePrice.toNumber(),
dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0],
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
@@ -1070,6 +1070,7 @@ export class PortfolioService {
marketPriceMin,
SymbolProfile,
activities: [],
+ activitiesCount: 0,
averagePrice: 0,
dataProviderInfo: undefined,
dividendInBaseCurrency: 0,
@@ -1095,7 +1096,6 @@ export class PortfolioService {
},
quantity: 0,
tags: [],
- transactionCount: undefined,
value: 0
};
}
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
index 8f6616174..d4c1c59c1 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
+++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
@@ -100,6 +100,7 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces';
templateUrl: 'holding-detail-dialog.html'
})
export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
+ public activitiesCount: number;
public activityForm: FormGroup;
public accounts: Account[];
public assetClass: string;
@@ -151,8 +152,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public SymbolProfile: EnhancedSymbolProfile;
public tags: Tag[];
public tagsAvailable: Tag[];
- public totalItems: number;
- public transactionCount: number;
public user: User;
public value: number;
@@ -261,6 +260,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(
({
+ activitiesCount,
averagePrice,
dataProviderInfo,
dividendInBaseCurrency,
@@ -279,9 +279,9 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
quantity,
SymbolProfile,
tags,
- transactionCount,
value
}) => {
+ this.activitiesCount = activitiesCount;
this.averagePrice = averagePrice;
if (
@@ -429,8 +429,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
this.activityForm.setValue({ tags: this.tags }, { emitEvent: false });
- this.transactionCount = transactionCount;
- this.totalItems = transactionCount;
this.value = value;
if (SymbolProfile?.assetClass) {
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
index 651f01a65..298692303 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
+++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
@@ -223,9 +223,9 @@
- @if (transactionCount === 1) {
+ @if (activitiesCount === 1) {
Activity
} @else {
Activities
@@ -363,7 +363,7 @@
[sortColumn]="sortColumn"
[sortDirection]="sortDirection"
[sortDisabled]="true"
- [totalItems]="totalItems"
+ [totalItems]="activitiesCount"
(activityToClone)="onCloneActivity($event)"
(activityToUpdate)="onUpdateActivity($event)"
(export)="onExport()"
diff --git a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts
index 000460228..b82a8f85d 100644
--- a/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts
+++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts
@@ -10,6 +10,7 @@ import { Tag } from '@prisma/client';
export interface PortfolioHoldingResponse {
activities: Activity[];
+ activitiesCount: number;
averagePrice: number;
dataProviderInfo: DataProviderInfo;
dividendInBaseCurrency: number;
@@ -34,6 +35,5 @@ export interface PortfolioHoldingResponse {
quantity: number;
SymbolProfile: EnhancedSymbolProfile;
tags: Tag[];
- transactionCount: number;
value: number;
}
From df16aba5c69d3c53557e801b19616600f8cdb2b1 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 11 Oct 2025 20:37:37 +0200
Subject: [PATCH 11/32] Feature/upgrade prisma to version 6.17.1 (#5732)
* Upgrade prisma to version 6.17.1
* Update changelog
---
CHANGELOG.md | 1 +
package-lock.json | 72 +++++++++++++++++++++++------------------------
package.json | 4 +--
3 files changed, 39 insertions(+), 38 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d178ae97a..e26093a0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactored `transactionCount` to `activitiesCount` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol`
- Refactored various components to use self-closing tags
- Improved the language localization for German (`de`)
+- Upgraded `prisma` from version `6.16.1` to `6.16.3`
### Fixed
diff --git a/package-lock.json b/package-lock.json
index 55e453cce..0f313a101 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,7 +44,7 @@
"@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2",
- "@prisma/client": "6.16.3",
+ "@prisma/client": "6.17.1",
"@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0",
@@ -149,7 +149,7 @@
"nx": "21.5.1",
"prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0",
- "prisma": "6.16.3",
+ "prisma": "6.17.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"replace-in-file": "8.3.0",
@@ -11960,9 +11960,9 @@
"license": "MIT"
},
"node_modules/@prisma/client": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.16.3.tgz",
- "integrity": "sha512-JfNfAtXG+/lIopsvoZlZiH2k5yNx87mcTS4t9/S5oufM1nKdXYxOvpDC1XoTCFBa5cQh7uXnbMPsmZrwZY80xw==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz",
+ "integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
@@ -11982,9 +11982,9 @@
}
},
"node_modules/@prisma/config": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.3.tgz",
- "integrity": "sha512-VlsLnG4oOuKGGMToEeVaRhoTBZu5H3q51jTQXb/diRags3WV0+BQK5MolJTtP6G7COlzoXmWeS11rNBtvg+qFQ==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz",
+ "integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
@@ -11995,53 +11995,53 @@
}
},
"node_modules/@prisma/debug": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.3.tgz",
- "integrity": "sha512-89DdqWtdKd7qoc9/qJCKLTazj3W3zPEiz0hc7HfZdpjzm21c7orOUB5oHWJsG+4KbV4cWU5pefq3CuDVYF9vgA==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz",
+ "integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.3.tgz",
- "integrity": "sha512-b+Rl4nzQDcoqe6RIpSHv8f5lLnwdDGvXhHjGDiokObguAAv/O1KaX1Oc69mBW/GFWKQpCkOraobLjU6s1h8HGg==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz",
+ "integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
- "@prisma/debug": "6.16.3",
- "@prisma/engines-version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a",
- "@prisma/fetch-engine": "6.16.3",
- "@prisma/get-platform": "6.16.3"
+ "@prisma/debug": "6.17.1",
+ "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
+ "@prisma/fetch-engine": "6.17.1",
+ "@prisma/get-platform": "6.17.1"
}
},
"node_modules/@prisma/engines-version": {
- "version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a",
- "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a.tgz",
- "integrity": "sha512-fftRmosBex48Ph1v2ll1FrPpirwtPZpNkE5CDCY1Lw2SD2ctyrLlVlHiuxDAAlALwWBOkPbAll4+EaqdGuMhJw==",
+ "version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz",
+ "integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==",
"devOptional": true,
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.3.tgz",
- "integrity": "sha512-bUoRIkVaI+CCaVGrSfcKev0/Mk4ateubqWqGZvQ9uCqFv2ENwWIR3OeNuGin96nZn5+SkebcD7RGgKr/+mJelw==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz",
+ "integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
- "@prisma/debug": "6.16.3",
- "@prisma/engines-version": "6.16.1-1.bb420e667c1820a8c05a38023385f6cc7ef8e83a",
- "@prisma/get-platform": "6.16.3"
+ "@prisma/debug": "6.17.1",
+ "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
+ "@prisma/get-platform": "6.17.1"
}
},
"node_modules/@prisma/get-platform": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.3.tgz",
- "integrity": "sha512-X1LxiFXinJ4iQehrodGp0f66Dv6cDL0GbRlcCoLtSu6f4Wi+hgo7eND/afIs5029GQLgNWKZ46vn8hjyXTsHLA==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz",
+ "integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==",
"devOptional": true,
"license": "Apache-2.0",
"dependencies": {
- "@prisma/debug": "6.16.3"
+ "@prisma/debug": "6.17.1"
}
},
"node_modules/@redis/client": {
@@ -35747,15 +35747,15 @@
}
},
"node_modules/prisma": {
- "version": "6.16.3",
- "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.3.tgz",
- "integrity": "sha512-4tJq3KB9WRshH5+QmzOLV54YMkNlKOtLKaSdvraI5kC/axF47HuOw6zDM8xrxJ6s9o2WodY654On4XKkrobQdQ==",
+ "version": "6.17.1",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz",
+ "integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==",
"devOptional": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
- "@prisma/config": "6.16.3",
- "@prisma/engines": "6.16.3"
+ "@prisma/config": "6.17.1",
+ "@prisma/engines": "6.17.1"
},
"bin": {
"prisma": "build/index.js"
diff --git a/package.json b/package.json
index 5f99a4d11..b0a801bf8 100644
--- a/package.json
+++ b/package.json
@@ -90,7 +90,7 @@
"@nestjs/schedule": "6.0.0",
"@nestjs/serve-static": "5.0.3",
"@openrouter/ai-sdk-provider": "0.7.2",
- "@prisma/client": "6.16.3",
+ "@prisma/client": "6.17.1",
"@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0",
@@ -195,7 +195,7 @@
"nx": "21.5.1",
"prettier": "3.6.2",
"prettier-plugin-organize-attributes": "1.0.0",
- "prisma": "6.16.3",
+ "prisma": "6.17.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"replace-in-file": "8.3.0",
From a9f38aaf90451079b7db39e31db661932a6514ce Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 11 Oct 2025 20:38:09 +0200
Subject: [PATCH 12/32] Task/remove deprecated position endpoints from
portfolio controller (#5733)
* Remove deprecated endpoints
* GET api/v1/portfolio/position/:dataSource/:symbol
* PUT api/v1/portfolio/position/:dataSource/:symbol/tags
* Update changelog
---
CHANGELOG.md | 2 +
.../src/app/portfolio/portfolio.controller.ts | 66 -------------------
2 files changed, 2 insertions(+), 66 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e26093a0c..01883f0f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changed the _As seen in_ section on the landing page to an animated carousel
- Refactored `transactionCount` to `activitiesCount` in the endpoint `GET api/v1/portfolio/holding/:dataSource/:symbol`
- Refactored various components to use self-closing tags
+- Removed the deprecated endpoint `GET api/v1/portfolio/position/:dataSource/:symbol`
+- Removed the deprecated endpoint `PUT api/v1/portfolio/position/:dataSource/:symbol/tags`
- Improved the language localization for German (`de`)
- Upgraded `prisma` from version `6.16.1` to `6.16.3`
diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts
index 5659818a8..19b0636c7 100644
--- a/apps/api/src/app/portfolio/portfolio.controller.ts
+++ b/apps/api/src/app/portfolio/portfolio.controller.ts
@@ -610,36 +610,6 @@ export class PortfolioController {
return performanceInformation;
}
- /**
- * @deprecated
- */
- @Get('position/:dataSource/:symbol')
- @UseInterceptors(RedactValuesInResponseInterceptor)
- @UseInterceptors(TransformDataSourceInRequestInterceptor)
- @UseInterceptors(TransformDataSourceInResponseInterceptor)
- @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
- public async getPosition(
- @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
- @Param('dataSource') dataSource: DataSource,
- @Param('symbol') symbol: string
- ): Promise {
- const holding = await this.portfolioService.getHolding({
- dataSource,
- impersonationId,
- symbol,
- userId: this.request.user.id
- });
-
- if (!holding) {
- throw new HttpException(
- getReasonPhrase(StatusCodes.NOT_FOUND),
- StatusCodes.NOT_FOUND
- );
- }
-
- return holding;
- }
-
@Get('report')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getReport(
@@ -699,40 +669,4 @@ export class PortfolioController {
userId: this.request.user.id
});
}
-
- /**
- * @deprecated
- */
- @HasPermission(permissions.updateOrder)
- @Put('position/:dataSource/:symbol/tags')
- @UseInterceptors(TransformDataSourceInRequestInterceptor)
- @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
- public async updatePositionTags(
- @Body() data: UpdateHoldingTagsDto,
- @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
- @Param('dataSource') dataSource: DataSource,
- @Param('symbol') symbol: string
- ): Promise {
- const holding = await this.portfolioService.getHolding({
- dataSource,
- impersonationId,
- symbol,
- userId: this.request.user.id
- });
-
- if (!holding) {
- throw new HttpException(
- getReasonPhrase(StatusCodes.NOT_FOUND),
- StatusCodes.NOT_FOUND
- );
- }
-
- await this.portfolioService.updateTags({
- dataSource,
- impersonationId,
- symbol,
- tags: data.tags,
- userId: this.request.user.id
- });
- }
}
From fc4d5774fa586f9eff75f2e1db3d6b67a4f05347 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?=
Date: Sat, 11 Oct 2025 20:42:40 +0200
Subject: [PATCH 13/32] =?UTF-8?q?Bugfix/enable=20IPv6=20connectivity=C2=A0?=
=?UTF-8?q?for=20Redis=20in=20job=20queue=20module=20(#5726)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Enable IPv6 connectivity for Redis in job queue module
* Update changelog
---
CHANGELOG.md | 1 +
apps/api/src/app/app.module.ts | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01883f0f3..6139cbbab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixed the server startup message to properly display IPv6 addresses
+- Enabled IPv6 connectivity for _Redis_ in the job queue module by setting the address family
- Fixed an issue where importing custom asset profiles failed due to validation errors
## 2.207.0 - 2025-10-08
diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts
index 8596aa0eb..86ceede28 100644
--- a/apps/api/src/app/app.module.ts
+++ b/apps/api/src/app/app.module.ts
@@ -71,9 +71,10 @@ import { UserModule } from './user/user.module';
BullModule.forRoot({
redis: {
db: parseInt(process.env.REDIS_DB ?? '0', 10),
+ family: 0,
host: process.env.REDIS_HOST,
- port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
- password: process.env.REDIS_PASSWORD
+ password: process.env.REDIS_PASSWORD,
+ port: parseInt(process.env.REDIS_PORT ?? '6379', 10)
}
}),
CacheModule,
From c1c7d82c836b4bc345055bba4090de7527d6c072 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sat, 11 Oct 2025 20:44:59 +0200
Subject: [PATCH 14/32] Release 2.208.0 (#5734)
---
CHANGELOG.md | 2 +-
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6139cbbab..ce142e8e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## Unreleased
+## 2.208.0 - 2025-10-11
### Added
diff --git a/package-lock.json b/package-lock.json
index 0f313a101..75e1f50fe 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ghostfolio",
- "version": "2.207.0",
+ "version": "2.208.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
- "version": "2.207.0",
+ "version": "2.208.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
diff --git a/package.json b/package.json
index b0a801bf8..28968c2c0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ghostfolio",
- "version": "2.207.0",
+ "version": "2.208.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
From 20da95239ec211f3f834ee5014b5a3fd735f2dfb Mon Sep 17 00:00:00 2001
From: Lagmator22 <133108827+Lagmator22@users.noreply.github.com>
Date: Sun, 12 Oct 2025 02:18:18 +0530
Subject: [PATCH 15/32] Task/refactor liabilities to liabilitiesInBaseCurrency
in portfolio summary interface (#5725)
* Refactor liabilities to liabilitiesInBaseCurrency
---
apps/api/src/app/portfolio/portfolio.service.ts | 2 +-
.../portfolio-summary/portfolio-summary.component.html | 7 +++++--
.../src/lib/interfaces/portfolio-summary.interface.ts | 2 +-
3 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts
index f774378ef..a5bc10fbd 100644
--- a/apps/api/src/app/portfolio/portfolio.service.ts
+++ b/apps/api/src/app/portfolio/portfolio.service.ts
@@ -2106,7 +2106,7 @@ export class PortfolioService {
.plus(fees)
.toNumber(),
interest: interest.toNumber(),
- liabilities: liabilities.toNumber(),
+ liabilitiesInBaseCurrency: liabilities.toNumber(),
totalInvestment: totalInvestment.toNumber(),
totalValueInBaseCurrency: netWorth
};
diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
index 19f125523..c8d710019 100644
--- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
+++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
@@ -242,7 +242,10 @@
Liabilities
- @if (summary?.liabilities || summary?.liabilities === 0) {
+ @if (
+ summary?.liabilitiesInBaseCurrency ||
+ summary?.liabilitiesInBaseCurrency === 0
+ ) {
-
}
diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts
index 05fac0ba0..092a4bb97 100644
--- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts
+++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts
@@ -21,7 +21,7 @@ export interface PortfolioSummary extends PortfolioPerformance {
grossPerformance: number;
grossPerformanceWithCurrencyEffect: number;
interest: number;
- liabilities: number;
+ liabilitiesInBaseCurrency: number;
totalBuy: number;
totalSell: number;
totalValueInBaseCurrency?: number;
From 02ef4dc8d9daad84044f3d6b80e0adc468eeb111 Mon Sep 17 00:00:00 2001
From: steffenrapp <88974099+steffenrapp@users.noreply.github.com>
Date: Sun, 12 Oct 2025 08:41:32 +0200
Subject: [PATCH 16/32] Task/disable zoom in PWA (#5735)
* Disable zoom in PWA
* Update changelog
---
CHANGELOG.md | 6 ++++++
apps/client/src/index.html | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce142e8e8..c36963d66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### Changed
+
+- Disabled zoom in PWA
+
## 2.208.0 - 2025-10-11
### Added
diff --git a/apps/client/src/index.html b/apps/client/src/index.html
index cfdcbc4c8..c923e3e0c 100644
--- a/apps/client/src/index.html
+++ b/apps/client/src/index.html
@@ -13,7 +13,7 @@
From 02ca2eca284d5572848063cde976ef057a8062e8 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Oct 2025 08:56:13 +0200
Subject: [PATCH 17/32] Task/improve changelog entry (#5738)
* Update changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c36963d66..28818fdfe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
-- Disabled zoom in PWA
+- Disabled the zoom functionality in the _Progressive Web App_ (PWA)
## 2.208.0 - 2025-10-11
From 5c7d34821e465017ff5e77a361230b47bb0c7eb1 Mon Sep 17 00:00:00 2001
From: David Requeno <108202767+DavidReque@users.noreply.github.com>
Date: Sun, 12 Oct 2025 01:30:00 -0600
Subject: [PATCH 18/32] Feature/set up Storybook story for holdings table
component (#5697)
* Set up Storybook story for holdings table component
* Update changelog
---
CHANGELOG.md | 4 +
.../holdings-table.component.stories.ts | 62 ++++
libs/ui/src/lib/mocks/holdings.ts | 293 +++++++++++++++
.../treemap-chart.component.stories.ts | 351 +-----------------
4 files changed, 361 insertions(+), 349 deletions(-)
create mode 100644 libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts
create mode 100644 libs/ui/src/lib/mocks/holdings.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28818fdfe..529d6f83a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+- Added a _Storybook_ story for the holdings table component
+
### Changed
- Disabled the zoom functionality in the _Progressive Web App_ (PWA)
diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts
new file mode 100644
index 000000000..37e73e9e4
--- /dev/null
+++ b/libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts
@@ -0,0 +1,62 @@
+import { CommonModule } from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from '@angular/material/table';
+import { moduleMetadata } from '@storybook/angular';
+import type { Meta, StoryObj } from '@storybook/angular';
+import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+
+import { GfEntityLogoComponent } from '../entity-logo';
+import { holdings } from '../mocks/holdings';
+import { GfValueComponent } from '../value';
+import { GfHoldingsTableComponent } from './holdings-table.component';
+
+export default {
+ title: 'Holdings Table',
+ component: GfHoldingsTableComponent,
+ decorators: [
+ moduleMetadata({
+ imports: [
+ CommonModule,
+ GfEntityLogoComponent,
+ GfValueComponent,
+ MatButtonModule,
+ MatDialogModule,
+ MatPaginatorModule,
+ MatSortModule,
+ MatTableModule,
+ NgxSkeletonLoaderModule
+ ]
+ })
+ ]
+} as Meta;
+
+type Story = StoryObj;
+
+export const Loading: Story = {
+ args: {
+ holdings: undefined,
+ baseCurrency: 'USD',
+ deviceType: 'desktop',
+ hasPermissionToOpenDetails: false,
+ hasPermissionToShowQuantities: true,
+ hasPermissionToShowValues: true,
+ locale: 'en-US',
+ pageSize: Number.MAX_SAFE_INTEGER
+ }
+};
+
+export const Default: Story = {
+ args: {
+ holdings,
+ baseCurrency: 'USD',
+ deviceType: 'desktop',
+ hasPermissionToOpenDetails: false,
+ hasPermissionToShowQuantities: true,
+ hasPermissionToShowValues: true,
+ locale: 'en-US',
+ pageSize: Number.MAX_SAFE_INTEGER
+ }
+};
diff --git a/libs/ui/src/lib/mocks/holdings.ts b/libs/ui/src/lib/mocks/holdings.ts
new file mode 100644
index 000000000..6e8485c82
--- /dev/null
+++ b/libs/ui/src/lib/mocks/holdings.ts
@@ -0,0 +1,293 @@
+import { PortfolioPosition } from '@ghostfolio/common/interfaces';
+
+export const holdings: PortfolioPosition[] = [
+ {
+ allocationInPercentage: 0.042990776363386086,
+ assetClass: 'EQUITY' as any,
+ assetClassLabel: 'Equity',
+ assetSubClass: 'STOCK' as any,
+ assetSubClassLabel: 'Stock',
+ countries: [
+ {
+ code: 'US',
+ weight: 1,
+ continent: 'North America',
+ name: 'United States'
+ }
+ ],
+ currency: 'USD',
+ dataSource: 'YAHOO' as any,
+ dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 3856,
+ grossPerformancePercent: 0.46047289228564603,
+ grossPerformancePercentWithCurrencyEffect: 0.46047289228564603,
+ grossPerformanceWithCurrencyEffect: 3856,
+ holdings: [],
+ investment: 8374,
+ marketPrice: 244.6,
+ name: 'Apple Inc',
+ netPerformance: 3855,
+ netPerformancePercent: 0.460353475041796,
+ netPerformancePercentWithCurrencyEffect: 0.036440677966101696,
+ netPerformanceWithCurrencyEffect: 430,
+ quantity: 50,
+ sectors: [
+ {
+ name: 'Technology',
+ weight: 1
+ }
+ ],
+ symbol: 'AAPL',
+ tags: [],
+ transactionCount: 1,
+ url: 'https://www.apple.com',
+ valueInBaseCurrency: 12230
+ },
+ {
+ allocationInPercentage: 0.02377401948293552,
+ assetClass: 'EQUITY' as any,
+ assetClassLabel: 'Equity',
+ assetSubClass: 'STOCK' as any,
+ assetSubClassLabel: 'Stock',
+ countries: [
+ {
+ code: 'DE',
+ weight: 1,
+ continent: 'Europe',
+ name: 'Germany'
+ }
+ ],
+ currency: 'EUR',
+ dataSource: 'YAHOO' as any,
+ dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'),
+ dividend: 192,
+ grossPerformance: 2226.700251889169,
+ grossPerformancePercent: 0.49083842309827874,
+ grossPerformancePercentWithCurrencyEffect: 0.29306136948826367,
+ grossPerformanceWithCurrencyEffect: 1532.8272791336772,
+ holdings: [],
+ investment: 4536.523929471033,
+ marketPrice: 322.2,
+ name: 'Allianz SE',
+ netPerformance: 2222.2921914357685,
+ netPerformancePercent: 0.48986674069961134,
+ netPerformancePercentWithCurrencyEffect: 0.034489367670592026,
+ netPerformanceWithCurrencyEffect: 225.48257403052068,
+ quantity: 20,
+ sectors: [
+ {
+ name: 'Financial Services',
+ weight: 1
+ }
+ ],
+ symbol: 'ALV.DE',
+ tags: [],
+ transactionCount: 2,
+ url: 'https://www.allianz.com',
+ valueInBaseCurrency: 6763.224181360202
+ },
+ {
+ allocationInPercentage: 0.08038536990007467,
+ assetClass: 'EQUITY' as any,
+ assetClassLabel: 'Equity',
+ assetSubClass: 'STOCK' as any,
+ assetSubClassLabel: 'Stock',
+ countries: [
+ {
+ code: 'US',
+ weight: 1,
+ continent: 'North America',
+ name: 'United States'
+ }
+ ],
+ currency: 'USD',
+ dataSource: 'YAHOO' as any,
+ dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 12758.05,
+ grossPerformancePercent: 1.2619300787837724,
+ grossPerformancePercentWithCurrencyEffect: 1.2619300787837724,
+ grossPerformanceWithCurrencyEffect: 12758.05,
+ holdings: [],
+ investment: 10109.95,
+ marketPrice: 228.68,
+ name: 'Amazon.com, Inc.',
+ netPerformance: 12677.26,
+ netPerformancePercent: 1.253938941339967,
+ netPerformancePercentWithCurrencyEffect: -0.037866008722316276,
+ netPerformanceWithCurrencyEffect: -899.99926757812,
+ quantity: 100,
+ sectors: [
+ {
+ name: 'Consumer Discretionary',
+ weight: 1
+ }
+ ],
+ symbol: 'AMZN',
+ tags: [],
+ transactionCount: 1,
+ url: 'https://www.aboutamazon.com',
+ valueInBaseCurrency: 22868
+ },
+ {
+ allocationInPercentage: 0.19216416482928922,
+ assetClass: 'LIQUIDITY' as any,
+ assetClassLabel: 'Liquidity',
+ assetSubClass: 'CRYPTOCURRENCY' as any,
+ assetSubClassLabel: 'Cryptocurrency',
+ countries: [],
+ currency: 'USD',
+ dataSource: 'COINGECKO' as any,
+ dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 52666.7898248,
+ grossPerformancePercent: 26.333394912400003,
+ grossPerformancePercentWithCurrencyEffect: 26.333394912400003,
+ grossPerformanceWithCurrencyEffect: 52666.7898248,
+ holdings: [],
+ investment: 1999.9999999999998,
+ marketPrice: 97364,
+ name: 'Bitcoin',
+ netPerformance: 52636.8898248,
+ netPerformancePercent: 26.3184449124,
+ netPerformancePercentWithCurrencyEffect: -0.04760906442310894,
+ netPerformanceWithCurrencyEffect: -2732.737808972287,
+ quantity: 0.5614682,
+ sectors: [],
+ symbol: 'bitcoin',
+ tags: [],
+ transactionCount: 1,
+ url: null,
+ valueInBaseCurrency: 54666.7898248
+ },
+ {
+ allocationInPercentage: 0.04307127421937313,
+ assetClass: 'EQUITY' as any,
+ assetClassLabel: 'Equity',
+ assetSubClass: 'STOCK' as any,
+ assetSubClassLabel: 'Stock',
+ countries: [
+ {
+ code: 'US',
+ weight: 1,
+ continent: 'North America',
+ name: 'United States'
+ }
+ ],
+ currency: 'USD',
+ dataSource: 'YAHOO' as any,
+ dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 5065.5,
+ grossPerformancePercent: 0.7047750229568411,
+ grossPerformancePercentWithCurrencyEffect: 0.7047750229568411,
+ grossPerformanceWithCurrencyEffect: 5065.5,
+ holdings: [],
+ investment: 7187.4,
+ marketPrice: 408.43,
+ name: 'Microsoft Corporation',
+ netPerformance: 5065.5,
+ netPerformancePercent: 0.7047750229568411,
+ netPerformancePercentWithCurrencyEffect: -0.015973588391056275,
+ netPerformanceWithCurrencyEffect: -198.899926757814,
+ quantity: 30,
+ sectors: [
+ {
+ name: 'Technology',
+ weight: 1
+ }
+ ],
+ symbol: 'MSFT',
+ tags: [],
+ transactionCount: 1,
+ url: 'https://www.microsoft.com',
+ valueInBaseCurrency: 12252.9
+ },
+ {
+ allocationInPercentage: 0.18762679306394897,
+ assetClass: 'EQUITY' as any,
+ assetClassLabel: 'Equity',
+ assetSubClass: 'STOCK' as any,
+ assetSubClassLabel: 'Stock',
+ countries: [
+ {
+ code: 'US',
+ weight: 1,
+ continent: 'North America',
+ name: 'United States'
+ }
+ ],
+ currency: 'USD',
+ dataSource: 'YAHOO' as any,
+ dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 51227.500000005,
+ grossPerformancePercent: 23.843379101756675,
+ grossPerformancePercentWithCurrencyEffect: 23.843379101756675,
+ grossPerformanceWithCurrencyEffect: 51227.500000005,
+ holdings: [],
+ investment: 2148.499999995,
+ marketPrice: 355.84,
+ name: 'Tesla, Inc.',
+ netPerformance: 51197.500000005,
+ netPerformancePercent: 23.829415871596066,
+ netPerformancePercentWithCurrencyEffect: -0.12051410125545206,
+ netPerformanceWithCurrencyEffect: -7314.00091552734,
+ quantity: 150,
+ sectors: [
+ {
+ name: 'Consumer Discretionary',
+ weight: 1
+ }
+ ],
+ symbol: 'TSLA',
+ tags: [],
+ transactionCount: 1,
+ url: 'https://www.tesla.com',
+ valueInBaseCurrency: 53376
+ },
+ {
+ allocationInPercentage: 0.053051250766657634,
+ assetClass: 'EQUITY' as any,
+ assetClassLabel: 'Equity',
+ assetSubClass: 'ETF' as any,
+ assetSubClassLabel: 'ETF',
+ countries: [
+ {
+ code: 'US',
+ weight: 1,
+ continent: 'North America',
+ name: 'United States'
+ }
+ ],
+ currency: 'USD',
+ dataSource: 'YAHOO' as any,
+ dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'),
+ dividend: 0,
+ grossPerformance: 6845.8,
+ grossPerformancePercent: 1.0164758094605268,
+ grossPerformancePercentWithCurrencyEffect: 1.0164758094605268,
+ grossPerformanceWithCurrencyEffect: 6845.8,
+ holdings: [],
+ investment: 8246.2,
+ marketPrice: 301.84,
+ name: 'Vanguard Total Stock Market Index Fund ETF Shares',
+ netPerformance: 6746.3,
+ netPerformancePercent: 1.0017018833976383,
+ netPerformancePercentWithCurrencyEffect: 0.01085061564051406,
+ netPerformanceWithCurrencyEffect: 161.99969482422,
+ quantity: 50,
+ sectors: [
+ {
+ name: 'Equity',
+ weight: 1
+ }
+ ],
+ symbol: 'VTI',
+ tags: [],
+ transactionCount: 5,
+ url: 'https://www.vanguard.com',
+ valueInBaseCurrency: 15092
+ }
+];
diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts
index 2000b4f98..c8951ce6b 100644
--- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts
+++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts
@@ -4,6 +4,7 @@ import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
+import { holdings } from '../mocks/holdings';
import { GfTreemapChartComponent } from './treemap-chart.component';
export default {
@@ -34,359 +35,11 @@ type Story = StoryObj;
export const Default: Story = {
args: {
+ holdings,
baseCurrency: 'USD',
colorScheme: 'LIGHT',
cursor: undefined,
dateRange: 'mtd',
- holdings: [
- {
- allocationInPercentage: 0.042990776363386086,
- assetClass: 'EQUITY',
- assetSubClass: 'STOCK',
- countries: [],
- currency: 'USD',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2021-12-01T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 3856,
- grossPerformancePercent: 0.46047289228564603,
- grossPerformancePercentWithCurrencyEffect: 0.46047289228564603,
- grossPerformanceWithCurrencyEffect: 3856,
- holdings: [],
- investment: 8374,
- marketPrice: 244.6,
- name: 'Apple Inc',
- netPerformance: 3855,
- netPerformancePercent: 0.460353475041796,
- netPerformancePercentWithCurrencyEffect: 0.036440677966101696,
- netPerformanceWithCurrencyEffect: 430,
- quantity: 50,
- sectors: [],
- symbol: 'AAPL',
- tags: [],
- transactionCount: 1,
- url: 'https://www.apple.com',
- valueInBaseCurrency: 12230
- },
- {
- allocationInPercentage: 0.02377401948293552,
- assetClass: 'EQUITY',
- assetSubClass: 'STOCK',
- countries: [],
- currency: 'EUR',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2021-04-23T00:00:00.000Z'),
- dividend: 192,
- grossPerformance: 2226.700251889169,
- grossPerformancePercent: 0.49083842309827874,
- grossPerformancePercentWithCurrencyEffect: 0.29306136948826367,
- grossPerformanceWithCurrencyEffect: 1532.8272791336772,
- holdings: [],
- investment: 4536.523929471033,
- marketPrice: 322.2,
- name: 'Allianz SE',
- netPerformance: 2222.2921914357685,
- netPerformancePercent: 0.48986674069961134,
- netPerformancePercentWithCurrencyEffect: 0.034489367670592026,
- netPerformanceWithCurrencyEffect: 225.48257403052068,
- quantity: 20,
- sectors: [],
- symbol: 'ALV.DE',
- tags: [],
- transactionCount: 2,
- url: 'https://www.allianz.com',
- valueInBaseCurrency: 6763.224181360202
- },
- {
- allocationInPercentage: 0.08038536990007467,
- assetClass: 'EQUITY',
- assetSubClass: 'STOCK',
- countries: [],
- currency: 'USD',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2018-10-01T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 12758.05,
- grossPerformancePercent: 1.2619300787837724,
- grossPerformancePercentWithCurrencyEffect: 1.2619300787837724,
- grossPerformanceWithCurrencyEffect: 12758.05,
- holdings: [],
- investment: 10109.95,
- marketPrice: 228.68,
- name: 'Amazon.com, Inc.',
- netPerformance: 12677.26,
- netPerformancePercent: 1.253938941339967,
- netPerformancePercentWithCurrencyEffect: -0.037866008722316276,
- netPerformanceWithCurrencyEffect: -899.99926757812,
- quantity: 100,
- sectors: [],
- symbol: 'AMZN',
- tags: [],
- transactionCount: 1,
- url: 'https://www.aboutamazon.com',
- valueInBaseCurrency: 22868
- },
- {
- allocationInPercentage: 0.19216416482928922,
- assetClass: 'LIQUIDITY',
- assetSubClass: 'CRYPTOCURRENCY',
- countries: [],
- currency: 'USD',
- dataSource: 'COINGECKO',
- dateOfFirstActivity: new Date('2017-08-16T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 52666.7898248,
- grossPerformancePercent: 26.333394912400003,
- grossPerformancePercentWithCurrencyEffect: 26.333394912400003,
- grossPerformanceWithCurrencyEffect: 52666.7898248,
- holdings: [],
- investment: 1999.9999999999998,
- marketPrice: 97364,
- name: 'Bitcoin',
- netPerformance: 52636.8898248,
- netPerformancePercent: 26.3184449124,
- netPerformancePercentWithCurrencyEffect: -0.04760906442310894,
- netPerformanceWithCurrencyEffect: -2732.737808972287,
- quantity: 0.5614682,
- sectors: [],
- symbol: 'bitcoin',
- tags: [],
- transactionCount: 1,
- url: null,
- valueInBaseCurrency: 54666.7898248
- },
- {
- allocationInPercentage: 0.007378652850073097,
- assetClass: 'FIXED_INCOME',
- assetSubClass: 'BOND',
- countries: [],
- currency: 'EUR',
- dataSource: 'MANUAL',
- dateOfFirstActivity: new Date('2021-02-01T00:00:00.000Z'),
- dividend: 11.45,
- grossPerformance: 0,
- grossPerformancePercent: 0,
- grossPerformancePercentWithCurrencyEffect: -0.1247202380342517,
- grossPerformanceWithCurrencyEffect: -258.2576430160448,
- holdings: [],
- investment: 2099.0764063811926,
- marketPrice: 1,
- name: 'Bondora Go & Grow',
- netPerformance: 0,
- netPerformancePercent: 0,
- netPerformancePercentWithCurrencyEffect: 0.009445843828715519,
- netPerformanceWithCurrencyEffect: 19.6420125363184,
- quantity: 2000,
- sectors: [],
- symbol: 'BONDORA_GO_AND_GROW',
- tags: [],
- transactionCount: 5,
- url: null,
- valueInBaseCurrency: 2099.0764063811926
- },
- {
- allocationInPercentage: 0.07787531695543741,
- assetClass: 'EQUITY',
- assetSubClass: 'ETF',
- countries: [],
- currency: 'CHF',
- dataSource: 'MANUAL',
- dateOfFirstActivity: new Date('2021-04-01T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 4550.843985045582,
- grossPerformancePercent: 0.3631417324494093,
- grossPerformancePercentWithCurrencyEffect: 0.42037247857285137,
- grossPerformanceWithCurrencyEffect: 5107.057936556927,
- holdings: [],
- investment: 17603.097090932337,
- marketPrice: 188.22,
- name: 'frankly Extreme 95 Index',
- netPerformance: 4550.843985045582,
- netPerformancePercent: 0.3631417324494093,
- netPerformancePercentWithCurrencyEffect: 0.026190604904358043,
- netPerformanceWithCurrencyEffect: 565.4165171873152,
- quantity: 105.87328656807,
- sectors: [],
- symbol: 'FRANKLY95P',
- tags: [],
- transactionCount: 6,
- url: 'https://www.frankly.ch',
- valueInBaseCurrency: 22153.941075977917
- },
- {
- allocationInPercentage: 0.04307127421937313,
- assetClass: 'EQUITY',
- assetSubClass: 'STOCK',
- countries: [],
- currency: 'USD',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2023-01-03T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 5065.5,
- grossPerformancePercent: 0.7047750229568411,
- grossPerformancePercentWithCurrencyEffect: 0.7047750229568411,
- grossPerformanceWithCurrencyEffect: 5065.5,
- holdings: [],
- investment: 7187.4,
- marketPrice: 408.43,
- name: 'Microsoft Corporation',
- netPerformance: 5065.5,
- netPerformancePercent: 0.7047750229568411,
- netPerformancePercentWithCurrencyEffect: -0.015973588391056275,
- netPerformanceWithCurrencyEffect: -198.899926757814,
- quantity: 30,
- sectors: [],
- symbol: 'MSFT',
- tags: [],
- transactionCount: 1,
- url: 'https://www.microsoft.com',
- valueInBaseCurrency: 12252.9
- },
- {
- allocationInPercentage: 0.18762679306394897,
- assetClass: 'EQUITY',
- assetSubClass: 'STOCK',
- countries: [],
- currency: 'USD',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2017-01-03T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 51227.500000005,
- grossPerformancePercent: 23.843379101756675,
- grossPerformancePercentWithCurrencyEffect: 23.843379101756675,
- grossPerformanceWithCurrencyEffect: 51227.500000005,
- holdings: [],
- investment: 2148.499999995,
- marketPrice: 355.84,
- name: 'Tesla, Inc.',
- netPerformance: 51197.500000005,
- netPerformancePercent: 23.829415871596066,
- netPerformancePercentWithCurrencyEffect: -0.12051410125545206,
- netPerformanceWithCurrencyEffect: -7314.00091552734,
- quantity: 150,
- sectors: [],
- symbol: 'TSLA',
- tags: [],
- transactionCount: 1,
- url: 'https://www.tesla.com',
- valueInBaseCurrency: 53376
- },
- {
- allocationInPercentage: 0.053051250766657634,
- assetClass: 'EQUITY',
- assetSubClass: 'ETF',
- countries: [],
- currency: 'USD',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2019-03-01T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 6845.8,
- grossPerformancePercent: 1.0164758094605268,
- grossPerformancePercentWithCurrencyEffect: 1.0164758094605268,
- grossPerformanceWithCurrencyEffect: 6845.8,
- holdings: [],
- investment: 8246.2,
- marketPrice: 301.84,
- name: 'Vanguard Total Stock Market Index Fund ETF Shares',
- netPerformance: 6746.3,
- netPerformancePercent: 1.0017018833976383,
- netPerformancePercentWithCurrencyEffect: 0.01085061564051406,
- netPerformanceWithCurrencyEffect: 161.99969482422,
- quantity: 50,
- sectors: [],
- symbol: 'VTI',
- tags: [],
- transactionCount: 5,
- url: 'https://www.vanguard.com',
- valueInBaseCurrency: 15092
- },
- {
- allocationInPercentage: 0.0836576192450555,
- assetClass: 'EQUITY',
- assetSubClass: 'ETF',
- countries: [],
- currency: 'CHF',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2018-03-01T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 6462.42356864925,
- grossPerformancePercent: 0.5463044783973836,
- grossPerformancePercentWithCurrencyEffect: 0.6282343505275325,
- grossPerformanceWithCurrencyEffect: 7121.935580698947,
- holdings: [],
- investment: 17336.464702612564,
- marketPrice: 129.74,
- name: 'Vanguard FTSE All-World UCITS ETF',
- netPerformance: 6373.040578098944,
- netPerformancePercent: 0.5387484388540966,
- netPerformancePercentWithCurrencyEffect: 0.008409682389650015,
- netPerformanceWithCurrencyEffect: 198.47200506226807,
- quantity: 165,
- sectors: [],
- symbol: 'VWRL.SW',
- tags: [],
- transactionCount: 5,
- url: 'https://www.vanguard.com',
- valueInBaseCurrency: 23798.888271261814
- },
- {
- allocationInPercentage: 0.03265192235898284,
- assetClass: 'EQUITY',
- assetSubClass: 'ETF',
- countries: [],
- currency: 'EUR',
- dataSource: 'YAHOO',
- dateOfFirstActivity: new Date('2021-08-19T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 3112.7991183879094,
- grossPerformancePercent: 0.5040147846036197,
- grossPerformancePercentWithCurrencyEffect: 0.3516875105542396,
- grossPerformanceWithCurrencyEffect: 2416.799201046856,
- holdings: [],
- investment: 6176.007556675063,
- marketPrice: 118.005,
- name: 'Xtrackers MSCI World UCITS ETF 1C',
- netPerformance: 3081.4179261125105,
- netPerformancePercent: 0.4989336392216841,
- netPerformancePercentWithCurrencyEffect: 0.006460676966633529,
- netPerformanceWithCurrencyEffect: 59.626750161726044,
- quantity: 75,
- sectors: [],
- symbol: 'XDWD.DE',
- tags: [],
- transactionCount: 1,
- url: null,
- valueInBaseCurrency: 9288.806675062973
- },
- {
- allocationInPercentage: 0.17537283996478595,
- assetClass: 'LIQUIDITY',
- assetSubClass: 'CASH',
- countries: [],
- currency: 'USD',
- dataSource: 'MANUAL',
- dateOfFirstActivity: new Date('2021-04-01T00:00:00.000Z'),
- dividend: 0,
- grossPerformance: 0,
- grossPerformancePercent: 0,
- grossPerformancePercentWithCurrencyEffect: 0,
- grossPerformanceWithCurrencyEffect: 0,
- holdings: [],
- investment: 49890,
- marketPrice: 0,
- name: 'USD',
- netPerformance: 0,
- netPerformancePercent: 0,
- netPerformancePercentWithCurrencyEffect: 0,
- netPerformanceWithCurrencyEffect: 0,
- quantity: 0,
- sectors: [],
- symbol: 'USD',
- tags: [],
- transactionCount: 0,
- valueInBaseCurrency: 49890
- }
- ],
locale: 'en-US'
}
};
From 74a4d830c83916fc7fdf6501038f62e8d615f6b1 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Oct 2025 09:30:26 +0200
Subject: [PATCH 19/32] Feature/improve accounts table Storybook story (#5739)
* Improve Storybook story
---
.../src/lib/accounts-table/accounts-table.component.stories.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts
index 42f48fffd..aeda82fd9 100644
--- a/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts
+++ b/libs/ui/src/lib/accounts-table/accounts-table.component.stories.ts
@@ -112,6 +112,7 @@ export const Loading: Story = {
accounts: undefined,
baseCurrency: 'USD',
deviceType: 'desktop',
+ hasPermissionToOpenDetails: false,
locale: 'en-US',
showActions: false,
showAllocationInPercentage: false,
@@ -128,6 +129,7 @@ export const Default: Story = {
accounts,
baseCurrency: 'USD',
deviceType: 'desktop',
+ hasPermissionToOpenDetails: false,
locale: 'en-US',
showActions: false,
showAllocationInPercentage: false,
@@ -147,6 +149,7 @@ export const WithoutFooter: Story = {
accounts,
baseCurrency: 'USD',
deviceType: 'desktop',
+ hasPermissionToOpenDetails: false,
locale: 'en-US',
showActions: false,
showAllocationInPercentage: false,
From 8d6153fa52707d5e22a2d4d7687e88fcea1e1be2 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 12 Oct 2025 13:48:23 +0200
Subject: [PATCH 20/32] Feature/use asset profile resolutions in getQuotes() of
FMP service (#5743)
* Use asset profile resolutions in getQuotes()
* Update changelog
---
CHANGELOG.md | 1 +
.../financial-modeling-prep.service.ts | 60 ++++++++++++-------
2 files changed, 40 insertions(+), 21 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 529d6f83a..cee2662a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Disabled the zoom functionality in the _Progressive Web App_ (PWA)
+- Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service
## 2.208.0 - 2025-10-11
diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
index 8bb8f8327..3b0d8ba72 100644
--- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
+++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
@@ -12,6 +12,7 @@ import {
IDataProviderHistoricalResponse,
IDataProviderResponse
} from '@ghostfolio/api/services/interfaces/interfaces';
+import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import {
DEFAULT_CURRENCY,
REPLACE_NAME_PARTS
@@ -49,7 +50,8 @@ export class FinancialModelingPrepService implements DataProviderInterface {
public constructor(
private readonly configurationService: ConfigurationService,
- private readonly cryptocurrencyService: CryptocurrencyService
+ private readonly cryptocurrencyService: CryptocurrencyService,
+ private readonly prismaService: PrismaService
) {
this.apiKey = this.configurationService.get(
'API_KEY_FINANCIAL_MODELING_PREP'
@@ -220,7 +222,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
public getDataProviderInfo(): DataProviderInfo {
return {
- dataSource: DataSource.FINANCIAL_MODELING_PREP,
+ dataSource: this.getName(),
isPremium: true,
name: 'Financial Modeling Prep',
url: 'https://financialmodelingprep.com/developer/docs'
@@ -359,25 +361,41 @@ export class FinancialModelingPrepService implements DataProviderInterface {
[symbol: string]: Pick;
} = {};
- const quotes = await fetch(
- `${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
- {
- signal: AbortSignal.timeout(requestTimeout)
- }
- ).then((res) => res.json());
+ const [assetProfileResolutions, quotes] = await Promise.all([
+ this.prismaService.assetProfileResolution.findMany({
+ where: {
+ dataSourceTarget: this.getDataProviderInfo().dataSource,
+ symbolTarget: { in: symbols }
+ }
+ }),
+ fetch(
+ `${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
+ {
+ signal: AbortSignal.timeout(requestTimeout)
+ }
+ ).then((res) => res.json())
+ ]);
- await Promise.all(
- quotes.map(({ symbol }) => {
- return this.getAssetProfile({
- requestTimeout,
- symbol
- }).then((assetProfile) => {
- if (assetProfile?.currency) {
- currencyBySymbolMap[symbol] = { currency: assetProfile.currency };
- }
- });
- })
- );
+ if (assetProfileResolutions.length === symbols.length) {
+ for (const { currency, symbolTarget } of assetProfileResolutions) {
+ currencyBySymbolMap[symbolTarget] = { currency };
+ }
+ } else {
+ await Promise.all(
+ quotes.map(({ symbol }) => {
+ return this.getAssetProfile({
+ requestTimeout,
+ symbol
+ }).then((assetProfile) => {
+ if (assetProfile?.currency) {
+ currencyBySymbolMap[symbol] = {
+ currency: assetProfile.currency
+ };
+ }
+ });
+ })
+ );
+ }
for (const { price, symbol } of quotes) {
let marketState: MarketState = 'delayed';
@@ -394,7 +412,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
marketState,
currency: currencyBySymbolMap[symbol]?.currency,
dataProviderInfo: this.getDataProviderInfo(),
- dataSource: DataSource.FINANCIAL_MODELING_PREP,
+ dataSource: this.getDataProviderInfo().dataSource,
marketPrice: price
};
}
From e8366603ad34d3ae707ebb63dbe6f5a624be80a1 Mon Sep 17 00:00:00 2001
From: Aditya Pawar <143347456+JustAdi10@users.noreply.github.com>
Date: Sun, 12 Oct 2025 17:19:28 +0530
Subject: [PATCH 21/32] Task/extract footer to component (#5702)
* Extract footer to component
* Update changelog
---
CHANGELOG.md | 1 +
apps/client/src/app/app.component.html | 188 +-----------------
apps/client/src/app/app.component.scss | 18 --
apps/client/src/app/app.component.ts | 35 ----
apps/client/src/app/app.module.ts | 7 +-
.../components/footer/footer.component.html | 181 +++++++++++++++++
.../components/footer/footer.component.scss | 16 ++
.../app/components/footer/footer.component.ts | 74 +++++++
8 files changed, 278 insertions(+), 242 deletions(-)
create mode 100644 apps/client/src/app/components/footer/footer.component.html
create mode 100644 apps/client/src/app/components/footer/footer.component.scss
create mode 100644 apps/client/src/app/components/footer/footer.component.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cee2662a2..9a555fa34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Disabled the zoom functionality in the _Progressive Web App_ (PWA)
- Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service
+- Extracted the footer to a component
## 2.208.0 - 2025-10-11
diff --git a/apps/client/src/app/app.component.html b/apps/client/src/app/app.component.html
index 2f5faa24c..61d5023e2 100644
--- a/apps/client/src/app/app.component.html
+++ b/apps/client/src/app/app.component.html
@@ -11,8 +11,8 @@
>
You are using the Live Demo.
Create Account
-
+
+
}
@if (!canCreateAccount && user?.systemMessage) {
@if (showFooter) {
-
+
From 3caa3c010efc289d1b13a76349110de34f782f86 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Thu, 16 Oct 2025 17:46:26 +0200
Subject: [PATCH 28/32] Bugfix/dark mode in logo carousel component (#5758)
* Fix dark mode
* Update changelog
---
CHANGELOG.md | 1 +
.../logo-carousel.component.scss | 20 +++++++------------
2 files changed, 8 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5024d0541..adc9dbf53 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service
- Fixed an issue where the scroll position was not restored when changing pages
+- Fixed the dark mode in the _As seen in_ section on the landing page
## 2.208.0 - 2025-10-11
diff --git a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss
index d8a8865f7..18c3a26cb 100644
--- a/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss
+++ b/libs/ui/src/lib/logo-carousel/logo-carousel.component.scss
@@ -194,19 +194,13 @@
);
}
- .logo {
- &.logo-alternative-to,
- &.logo-dev-community,
- &.logo-hacker-news,
- &.logo-openalternative,
- &.logo-privacy-tools,
- &.logo-reddit,
- &.logo-sackgeld,
- &.logo-selfh-st,
- &.logo-sourceforge,
- &.logo-umbrel,
- &.logo-unraid {
- background-color: rgba(var(--light-primary-text));
+ .logo-carousel-track {
+ .logo-carousel-item {
+ .logo {
+ &.mask {
+ background-color: rgba(var(--light-secondary-text));
+ }
+ }
}
}
}
From db2c2426c648ec39e5d9393c3b0e80c63dad506c Mon Sep 17 00:00:00 2001
From: Dibyendu Sahoo
Date: Fri, 17 Oct 2025 00:16:24 +0530
Subject: [PATCH 29/32] Task/refactor interest to interestInBaseCurrency in
portfolio summary interface (#5763)
* Refactor interest to interestInBaseCurrency
---
apps/api/src/app/portfolio/portfolio.controller.ts | 2 +-
apps/api/src/app/portfolio/portfolio.service.ts | 2 +-
apps/api/src/helper/object.helper.spec.ts | 4 ++--
.../portfolio-summary/portfolio-summary.component.html | 2 +-
libs/common/src/lib/interfaces/portfolio-summary.interface.ts | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts
index 19b0636c7..f6f8e3d80 100644
--- a/apps/api/src/app/portfolio/portfolio.controller.ts
+++ b/apps/api/src/app/portfolio/portfolio.controller.ts
@@ -197,7 +197,7 @@ export class PortfolioController {
'filteredValueInBaseCurrency',
'grossPerformance',
'grossPerformanceWithCurrencyEffect',
- 'interest',
+ 'interestInBaseCurrency',
'items',
'liabilities',
'netPerformance',
diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts
index a5bc10fbd..bbfb31b79 100644
--- a/apps/api/src/app/portfolio/portfolio.service.ts
+++ b/apps/api/src/app/portfolio/portfolio.service.ts
@@ -2105,7 +2105,7 @@ export class PortfolioService {
)
.plus(fees)
.toNumber(),
- interest: interest.toNumber(),
+ interestInBaseCurrency: interest.toNumber(),
liabilitiesInBaseCurrency: liabilities.toNumber(),
totalInvestment: totalInvestment.toNumber(),
totalValueInBaseCurrency: netWorth
diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts
index d7caf9bc9..433490325 100644
--- a/apps/api/src/helper/object.helper.spec.ts
+++ b/apps/api/src/helper/object.helper.spec.ts
@@ -1536,7 +1536,7 @@ describe('redactAttributes', () => {
fireWealth: null,
grossPerformance: null,
grossPerformanceWithCurrencyEffect: null,
- interest: null,
+ interestInBaseCurrency: null,
items: null,
liabilities: null,
totalInvestment: null,
@@ -3039,7 +3039,7 @@ describe('redactAttributes', () => {
fireWealth: null,
grossPerformance: null,
grossPerformanceWithCurrencyEffect: null,
- interest: null,
+ interestInBaseCurrency: null,
items: null,
liabilities: null,
totalInvestment: null,
diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
index c8d710019..b20b6b263 100644
--- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
+++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
@@ -302,7 +302,7 @@
[isCurrency]="true"
[locale]="locale"
[unit]="baseCurrency"
- [value]="isLoading ? undefined : summary?.interest"
+ [value]="isLoading ? undefined : summary?.interestInBaseCurrency"
/>
diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts
index 092a4bb97..f08eb61b8 100644
--- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts
+++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts
@@ -20,7 +20,7 @@ export interface PortfolioSummary extends PortfolioPerformance {
fireWealth: FireWealth;
grossPerformance: number;
grossPerformanceWithCurrencyEffect: number;
- interest: number;
+ interestInBaseCurrency: number;
liabilitiesInBaseCurrency: number;
totalBuy: number;
totalSell: number;
From 835bde6662c2aa5118fc0dccb38aa972caffb640 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Thu, 16 Oct 2025 20:51:14 +0200
Subject: [PATCH 30/32] Feature/extend pricing page (#5761)
* Extend pricing page
* Update changelog
---
CHANGELOG.md | 1 +
.../pages/pricing/pricing-page.component.ts | 10 +++++
.../src/app/pages/pricing/pricing-page.html | 43 +++++++++++++++----
3 files changed, 46 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index adc9dbf53..0fb55409a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Extended the glossary of the resources page by _Stealth Wealth_
+- Extended the content of the pricing page
- Added a _Storybook_ story for the holdings table component
### Changed
diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts
index 170d70914..8bc3e3a67 100644
--- a/apps/client/src/app/pages/pricing/pricing-page.component.ts
+++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts
@@ -69,6 +69,16 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
public professionalDataProviderTooltipPremium = translate(
'PROFESSIONAL_DATA_PROVIDER_TOOLTIP_PREMIUM'
);
+ public referralBrokers = [
+ 'DEGIRO',
+ 'finpension',
+ 'frankly',
+ 'Interactive Brokers',
+ 'Mintos',
+ 'Swissquote',
+ 'VIAC',
+ 'Zak'
+ ];
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkRegister = publicRoutes.register.routerLink;
public user: User;
diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html
index ea68b74eb..ee006b2d6 100644
--- a/apps/client/src/app/pages/pricing/pricing-page.html
+++ b/apps/client/src/app/pages/pricing/pricing-page.html
@@ -326,16 +326,43 @@
From ba1ee013d7868f549427df92bc379ab072bf372a Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Thu, 16 Oct 2025 20:51:39 +0200
Subject: [PATCH 31/32] Bugfix/fix word wrap in menus of activities table
(#5764)
* Fix word wrap
* Update changelog
---
CHANGELOG.md | 1 +
.../activities-table/activities-table.component.html | 12 ++++++++++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fb55409a..a024cc722 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Respected the include indices flag in the search functionality of the _Financial Modeling Prep_ service
- Fixed an issue where the scroll position was not restored when changing pages
+- Fixed the word wrap in the menus of the activities table component
- Fixed the dark mode in the _As seen in_ section on the landing page
## 2.208.0 - 2025-10-11
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html
index 472c24e2b..8079a6258 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.html
+++ b/libs/ui/src/lib/activities-table/activities-table.component.html
@@ -361,7 +361,11 @@
}
-
+
@if (hasPermissionToCreateActivity) {
}
-
+
|