Browse Source

Merge branch 'ghostfolio:main' into Task/lazy-load-platforms-through-API-#6113

pull/6130/head
Omkar Gujja 1 week ago
committed by GitHub
parent
commit
65a531ab0f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 36
      CHANGELOG.md
  2. 6
      README.md
  3. 1
      apps/api/src/app/info/info.service.ts
  4. 5
      apps/api/src/app/subscription/subscription.service.ts
  5. 22
      apps/api/src/helper/object.helper.spec.ts
  6. 11
      apps/api/src/helper/object.helper.ts
  7. 1
      apps/api/src/services/configuration/configuration.service.ts
  8. 2
      apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts
  9. 11
      apps/api/src/services/data-provider/manual/manual.service.ts
  10. 1
      apps/api/src/services/interfaces/environment.interface.ts
  11. 1
      apps/api/tsconfig.app.json
  12. 1
      apps/api/tsconfig.spec.json
  13. 1
      apps/client/project.json
  14. 2
      apps/client/src/app/components/access-table/access-table.component.ts
  15. 2
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  16. 3
      apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts
  17. 3
      apps/client/src/app/components/footer/footer.component.ts
  18. 2
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts
  19. 2
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts
  20. 20
      apps/client/src/app/components/user-account-membership/user-account-membership.component.ts
  21. 2
      apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts
  22. 3
      apps/client/src/app/pages/blog/blog-page.component.ts
  23. 17
      apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html
  24. 3
      apps/client/src/app/pages/markets/markets-page.component.ts
  25. 3
      apps/client/src/app/pages/open/open-page.component.ts
  26. 4
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  27. 24
      apps/client/src/app/pages/pricing/pricing-page.component.ts
  28. 2
      apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts
  29. 3
      apps/client/src/app/pages/zen/zen-page.component.ts
  30. 15
      apps/client/src/main.ts
  31. 1
      apps/client/tsconfig.app.json
  32. 2
      apps/client/tsconfig.json
  33. 3
      apps/client/tsconfig.spec.json
  34. 6
      libs/common/src/lib/interfaces/info-item.interface.ts
  35. 3
      libs/common/src/lib/interfaces/responses/create-stripe-checkout-session-response.interface.ts
  36. 2
      libs/common/src/lib/routes/routes.ts
  37. 6
      libs/common/tsconfig.json
  38. 3
      libs/common/tsconfig.spec.json
  39. 4
      libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts
  40. 8
      libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
  41. 2
      libs/ui/src/lib/logo-carousel/logo-carousel.component.ts
  42. 3
      libs/ui/tsconfig.json
  43. 2
      libs/ui/tsconfig.lib.json
  44. 3
      libs/ui/tsconfig.spec.json
  45. 4867
      package-lock.json
  46. 87
      package.json
  47. 3
      tsconfig.base.json

36
CHANGELOG.md

@ -5,22 +5,52 @@ 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.227.0 - 2026-01-02
### Changed
- Initialized the input properties in the _FIRE_ calculator
- Removed the deprecated public _Stripe_ key
- Upgraded `stripe` from version `18.5.0` to `20.1.0`
### Fixed
- Fixed the import of `jsonpath` to support REST APIs (`JSON`) via the scraper configuration
## 2.226.0 - 2026-01-01
### Added
- Extended the content of the _Self-Hosting_ section by information about additional data providers on the Frequently Asked Questions (FAQ) page
### Changed
- Upgraded `class-validator` from version `0.14.2` to `0.14.3`
- Upgraded `yahoo-finance2` from version `3.10.2` to `3.11.2`
## 2.225.0 - 2025-12-31
### Added
- Added a new endpoint to get all platforms (`GET api/v1/platforms`)
- Added the session url to the endpoint response of the _Stripe_ checkout
### Changed
- Improved the routing of the user detail dialog in the users section of the admin control panel
- Lifted the asset profile identifier editing restriction for `MANUAL` data sources in the asset profile details dialog of the admin control panel
- Deprecated the public _Stripe_ key
- Improved the language localization for German (`de`)
- Upgraded `angular` from version `20.2.4` to `20.3.9`
- Eliminated `ngx-stripe`
- Upgraded `angular` from version `20.2.4` to `21.0.6`
- Upgraded `marked` from version `15.0.4` to `17.0.1`
- Upgraded `ngx-device-detector` from version `10.1.0` to `11.0.0`
- Upgraded `ng-extract-i18n-merge` from `3.1.0` to `3.2.1`
- Upgraded `Nx` from version `21.5.1` to `22.1.3`
- Upgraded `ngx-markdown` from version `20.0.0` to `21.0.1`
- Upgraded `Nx` from version `21.5.1` to `22.3.3`
- Upgraded `shx` from version `0.3.4` to `0.4.0`
- Upgraded `storybook` from version `9.1.5` to `10.1.10`
- Upgraded `zone.js` from version `0.15.1` to `0.16.0`
### Fixed

6
README.md

@ -241,7 +241,7 @@ Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TO
| `accountId` | `string` (optional) | Id of the account |
| `comment` | `string` (optional) | Comment of the activity |
| `currency` | `string` | `CHF` \| `EUR` \| `USD` etc. |
| `dataSource` | `string` | `COINGECKO` \| `MANUAL` \| `YAHOO` |
| `dataSource` | `string` | `COINGECKO` \| `GHOSTFOLIO` [^1] \| `MANUAL` \| `YAHOO` |
| `date` | `string` | Date in the format `ISO-8601` |
| `fee` | `number` | Fee of the activity |
| `quantity` | `number` | Quantity of the activity |
@ -331,6 +331,8 @@ If you like to support this project, become a [**Sponsor**](https://github.com/s
## License
© 2021 - 2025 [Ghostfolio](https://ghostfol.io)
© 2021 - 2026 [Ghostfolio](https://ghostfol.io)
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).
[^1]: Available with [**Ghostfolio Premium**](https://ghostfol.io/en/pricing).

1
apps/api/src/app/info/info.service.ts

@ -93,7 +93,6 @@ export class InfoService {
(await this.propertyService.getByKey<string[]>(
PROPERTY_COUNTRIES_OF_SUBSCRIBERS
)) ?? [];
info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY');
}
if (this.configurationService.get('ENABLE_FEATURE_SYSTEM_MESSAGE')) {

5
apps/api/src/app/subscription/subscription.service.ts

@ -35,7 +35,7 @@ export class SubscriptionService {
this.stripe = new Stripe(
this.configurationService.get('STRIPE_SECRET_KEY'),
{
apiVersion: '2025-08-27.basil'
apiVersion: '2025-12-15.clover'
}
);
}
@ -100,7 +100,8 @@ export class SubscriptionService {
);
return {
sessionId: session.id
sessionId: session.id,
sessionUrl: session.url
};
}

22
apps/api/src/helper/object.helper.spec.ts

@ -1,4 +1,24 @@
import { redactAttributes } from './object.helper';
import { query, redactAttributes } from './object.helper';
describe('query', () => {
it('should get market price from stock API response', () => {
const object = {
currency: 'USD',
market: {
previousClose: 273.04,
price: 271.86
},
symbol: 'AAPL'
};
const result = query({
object,
pathExpression: '$.market.price'
})[0];
expect(result).toBe(271.86);
});
});
describe('redactAttributes', () => {
it('should redact provided attributes', () => {

11
apps/api/src/helper/object.helper.ts

@ -1,4 +1,5 @@
import { Big } from 'big.js';
import jsonpath from 'jsonpath';
import { cloneDeep, isArray, isObject } from 'lodash';
export function hasNotDefinedValuesInObject(aObject: Object): boolean {
@ -31,6 +32,16 @@ export function nullifyValuesInObjects<T>(aObjects: T[], keys: string[]): T[] {
});
}
export function query({
object,
pathExpression
}: {
object: object;
pathExpression: string;
}) {
return jsonpath.query(object, pathExpression);
}
export function redactAttributes({
isFirstRun = true,
object,

1
apps/api/src/services/configuration/configuration.service.ts

@ -102,7 +102,6 @@ export class ConfigurationService {
ROOT_URL: url({
default: environment.rootUrl
}),
STRIPE_PUBLIC_KEY: str({ default: '' }),
STRIPE_SECRET_KEY: str({ default: '' }),
TWITTER_ACCESS_TOKEN: str({ default: 'dummyAccessToken' }),
TWITTER_ACCESS_TOKEN_SECRET: str({ default: 'dummyAccessTokenSecret' }),

2
apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts

@ -18,7 +18,7 @@ import {
import { Injectable } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import * as Alphavantage from 'alphavantage';
import Alphavantage from 'alphavantage';
import { format, isAfter, isBefore, parse } from 'date-fns';
import { AlphaVantageHistoricalResponse } from './interfaces/interfaces';

11
apps/api/src/services/data-provider/manual/manual.service.ts

@ -1,3 +1,4 @@
import { query } from '@ghostfolio/api/helper/object.helper';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import {
DataProviderInterface,
@ -26,7 +27,6 @@ import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import * as cheerio from 'cheerio';
import { addDays, format, isBefore } from 'date-fns';
import * as jsonpath from 'jsonpath';
@Injectable()
export class ManualService implements DataProviderInterface {
@ -286,9 +286,14 @@ export class ManualService implements DataProviderInterface {
let value: string;
if (response.headers.get('content-type')?.includes('application/json')) {
const data = await response.json();
const object = await response.json();
value = String(jsonpath.query(data, scraperConfiguration.selector)[0]);
value = String(
query({
object,
pathExpression: scraperConfiguration.selector
})[0]
);
} else {
const $ = cheerio.load(await response.text());

1
apps/api/src/services/interfaces/environment.interface.ts

@ -52,7 +52,6 @@ export interface Environment extends CleanedEnvAccessors {
REDIS_PORT: number;
REQUEST_TIMEOUT: number;
ROOT_URL: string;
STRIPE_PUBLIC_KEY: string;
STRIPE_SECRET_KEY: string;
TWITTER_ACCESS_TOKEN: string;
TWITTER_ACCESS_TOKEN_SECRET: string;

1
apps/api/tsconfig.app.json

@ -4,6 +4,7 @@
"outDir": "../../dist/out-tsc",
"types": ["node"],
"emitDecoratorMetadata": true,
"moduleResolution": "node10",
"target": "es2021",
"module": "commonjs"
},

1
apps/api/tsconfig.spec.json

@ -3,6 +3,7 @@
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"moduleResolution": "node10",
"types": ["jest", "node"]
},
"include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts", "jest.config.ts"]

1
apps/client/project.json

@ -75,7 +75,6 @@
"ngswConfigPath": "apps/client/ngsw-config.json",
"optimization": false,
"polyfills": "apps/client/src/polyfills.ts",
"scripts": ["node_modules/marked/marked.min.js"],
"serviceWorker": true,
"sourceMap": true,
"styles": [

2
apps/client/src/app/components/access-table/access-table.component.ts

@ -4,7 +4,6 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@ -36,7 +35,6 @@ import ms from 'ms';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ClipboardModule,
CommonModule,
IonIcon,
MatButtonModule,
MatMenuModule,

2
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -29,7 +29,6 @@ import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplet
import { GfValueComponent } from '@ghostfolio/ui/value';
import { TextFieldModule } from '@angular/cdk/text-field';
import { CommonModule } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import {
ChangeDetectionStrategy,
@ -95,7 +94,6 @@ import { AssetProfileDialogParams } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column h-100' },
imports: [
CommonModule,
FormsModule,
GfCurrencySelectorComponent,
GfEntityLogoComponent,

3
apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts

@ -1,7 +1,6 @@
import { resolveFearAndGreedIndex } from '@ghostfolio/common/helper';
import { translate } from '@ghostfolio/ui/i18n';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@ -12,7 +11,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, NgxSkeletonLoaderModule],
imports: [NgxSkeletonLoaderModule],
selector: 'gf-fear-and-greed-index',
styleUrls: ['./fear-and-greed-index.component.scss'],
templateUrl: './fear-and-greed-index.component.html'

3
apps/client/src/app/components/footer/footer.component.ts

@ -3,7 +3,6 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@ -18,7 +17,7 @@ import { openOutline } from 'ionicons/icons';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, GfLogoComponent, IonIcon, RouterModule],
imports: [GfLogoComponent, IonIcon, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-footer',
styleUrls: ['./footer.component.scss'],

2
apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts

@ -4,7 +4,6 @@ import {
} from '@ghostfolio/client/services/settings-storage.service';
import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header';
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
@ -28,7 +27,6 @@ import { LoginWithAccessTokenDialogParams } from './interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
GfDialogHeaderComponent,
IonIcon,
MatButtonModule,

2
apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts

@ -2,7 +2,6 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfMembershipCardComponent } from '@ghostfolio/ui/membership-card';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@ -31,7 +30,6 @@ import { SubscriptionInterstitialDialogParams } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column flex-grow-1 h-100' },
imports: [
CommonModule,
GfMembershipCardComponent,
GfPremiumIndicatorComponent,
IonIcon,

20
apps/client/src/app/components/user-account-membership/user-account-membership.component.ts

@ -21,9 +21,8 @@ import { MatCardModule } from '@angular/material/card';
import { MatSnackBar } from '@angular/material/snack-bar';
import { RouterModule } from '@angular/router';
import ms, { StringValue } from 'ms';
import { StripeService } from 'ngx-stripe';
import { EMPTY, Subject } from 'rxjs';
import { catchError, switchMap, takeUntil } from 'rxjs/operators';
import { catchError, takeUntil } from 'rxjs/operators';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@ -62,7 +61,6 @@ export class GfUserAccountMembershipComponent implements OnDestroy {
private dataService: DataService,
private notificationService: NotificationService,
private snackBar: MatSnackBar,
private stripeService: StripeService,
private userService: UserService
) {
const { baseCurrency, globalPermissions } = this.dataService.fetchInfo();
@ -113,23 +111,17 @@ export class GfUserAccountMembershipComponent implements OnDestroy {
priceId: this.priceId
})
.pipe(
catchError((error) => {
catchError((error: Error) => {
this.notificationService.alert({
title: error.message
});
throw error;
return EMPTY;
}),
switchMap(({ sessionId }) => {
return this.stripeService.redirectToCheckout({ sessionId });
})
takeUntil(this.unsubscribeSubject)
)
.subscribe((result) => {
if (result.error) {
this.notificationService.alert({
title: result.error.message
});
}
.subscribe(({ sessionUrl }) => {
window.location.href = sessionUrl;
});
}

2
apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts

@ -4,7 +4,6 @@ import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer';
import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@ -26,7 +25,6 @@ import { UserDetailDialogParams } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column h-100' },
imports: [
CommonModule,
GfDialogFooterComponent,
GfDialogHeaderComponent,
GfValueComponent,

3
apps/client/src/app/pages/blog/blog-page.component.ts

@ -1,7 +1,6 @@
import { DataService } from '@ghostfolio/client/services/data.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { CommonModule } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA, OnDestroy } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router';
@ -12,7 +11,7 @@ import { Subject } from 'rxjs';
@Component({
host: { class: 'page' },
imports: [CommonModule, IonIcon, MatCardModule, RouterModule],
imports: [IonIcon, MatCardModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-blog-page',
styleUrls: ['./blog-page.scss'],

17
apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html

@ -135,9 +135,17 @@
<mat-card-title>Which data providers are supported?</mat-card-title>
</mat-card-header>
<mat-card-content
><code>COINGECKO</code>, <code>MANUAL</code> and
<code>YAHOO</code> are officially supported while all other data
providers are considered experimental.</mat-card-content
><code>COINGECKO</code>, <code>GHOSTFOLIO</code> (available with
<a
class="align-items-center d-inline-flex"
target="_blank"
[href]="pricingUrl"
>Ghostfolio Premium<gf-premium-indicator
class="d-inline-block ml-1"
[enableLink]="false" /></a
>), <code>MANUAL</code>, and <code>YAHOO</code> are officially
supported while all other data providers are considered
experimental.</mat-card-content
>
</mat-card>
<mat-card appearance="outlined" class="mb-3">
@ -147,7 +155,8 @@
>
</mat-card-header>
<mat-card-content
>Yes, access to a professional data provider is included with a
>Yes, access to a professional data provider (<code>GHOSTFOLIO</code>)
is included with a
<a
class="align-items-center d-inline-flex"
target="_blank"

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

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

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

@ -3,7 +3,6 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Statistics, User } from '@ghostfolio/common/interfaces';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import {
ChangeDetectorRef,
Component,
@ -16,7 +15,7 @@ import { Subject, takeUntil } from 'rxjs';
@Component({
host: { class: 'page' },
imports: [CommonModule, GfValueComponent, MatCardModule],
imports: [GfValueComponent, MatCardModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-open-page',
styleUrls: ['./open-page.scss'],

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

@ -96,7 +96,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
public isLoading = false;
public isToday = isToday;
public mode: 'create' | 'update';
public platforms: { id: string; name: string }[];
public tagsAvailable: Tag[] = [];
public total = 0;
public typesTranslationMap = new Map<Type, string>();
@ -127,11 +126,10 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.dateAdapter.setLocale(this.locale);
const { currencies, platforms } = this.dataService.fetchInfo();
const { currencies } = this.dataService.fetchInfo();
this.currencies = currencies;
this.defaultDateFormat = getDateFormatString(this.locale);
this.platforms = platforms;
this.dataService
.fetchPortfolioHoldings()

24
apps/client/src/app/pages/pricing/pricing-page.component.ts

@ -27,9 +27,8 @@ import {
informationCircleOutline
} from 'ionicons/icons';
import { StringValue } from 'ms';
import { StripeService } from 'ngx-stripe';
import { Subject } from 'rxjs';
import { catchError, switchMap, takeUntil } from 'rxjs/operators';
import { EMPTY, Subject } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators';
@Component({
host: { class: 'page' },
@ -98,7 +97,6 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private notificationService: NotificationService,
private stripeService: StripeService,
private userService: UserService
) {
addIcons({
@ -155,23 +153,17 @@ export class GfPricingPageComponent implements OnDestroy, OnInit {
priceId: this.priceId
})
.pipe(
switchMap(({ sessionId }) => {
return this.stripeService.redirectToCheckout({ sessionId });
}),
catchError((error) => {
catchError((error: Error) => {
this.notificationService.alert({
title: error.message
});
throw error;
})
return EMPTY;
}),
takeUntil(this.unsubscribeSubject)
)
.subscribe((result) => {
if (result.error) {
this.notificationService.alert({
title: result.error.message
});
}
.subscribe(({ sessionUrl }) => {
window.location.href = sessionUrl;
});
}

2
apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts

@ -3,7 +3,6 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { TextFieldModule } from '@angular/cdk/text-field';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@ -36,7 +35,6 @@ import { UserAccountRegistrationDialogParams } from './interfaces/interfaces';
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ClipboardModule,
CommonModule,
FormsModule,
IonIcon,
MatButtonModule,

3
apps/client/src/app/pages/zen/zen-page.component.ts

@ -2,7 +2,6 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { TabConfiguration, User } from '@ghostfolio/common/interfaces';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatTabsModule } from '@angular/material/tabs';
import { RouterModule } from '@angular/router';
@ -15,7 +14,7 @@ import { takeUntil } from 'rxjs/operators';
@Component({
host: { class: 'page has-tabs' },
imports: [CommonModule, IonIcon, MatTabsModule, RouterModule],
imports: [IonIcon, MatTabsModule, RouterModule],
selector: 'gf-zen-page',
styleUrls: ['./zen-page.scss'],
templateUrl: './zen-page.html'

15
apps/client/src/main.ts

@ -7,7 +7,11 @@ import {
provideHttpClient,
withInterceptorsFromDi
} from '@angular/common/http';
import { enableProdMode, importProvidersFrom } from '@angular/core';
import {
enableProdMode,
importProvidersFrom,
provideZoneChangeDetection
} from '@angular/core';
import {
DateAdapter,
MAT_DATE_FORMATS,
@ -23,7 +27,6 @@ import { ServiceWorkerModule } from '@angular/service-worker';
import { provideIonicAngular } from '@ionic/angular/standalone';
import { provideMarkdown } from 'ngx-markdown';
import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader';
import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe';
import { CustomDateAdapter } from './app/adapter/custom-date-adapter';
import { DateFormats } from './app/adapter/date-formats';
@ -50,8 +53,6 @@ import { environment } from './environments/environment';
(window as any).info = info;
environment.stripePublicKey = info.stripePublicKey;
if (environment.production) {
enableProdMode();
}
@ -65,7 +66,6 @@ import { environment } from './environments/environment';
MatNativeDateModule,
MatSnackBarModule,
MatTooltipModule,
NgxStripeModule.forRoot(environment.stripePublicKey),
RouterModule.forRoot(routes, {
anchorScrolling: 'enabled',
preloadingStrategy: ModulePreloadService,
@ -83,6 +83,7 @@ import { environment } from './environments/environment';
provideIonicAngular(),
provideMarkdown(),
provideNgxSkeletonLoader(),
provideZoneChangeDetection(),
{
deps: [LanguageService, MAT_DATE_LOCALE, Platform],
provide: DateAdapter,
@ -92,10 +93,6 @@ import { environment } from './environments/environment';
provide: MAT_DATE_FORMATS,
useValue: DateFormats
},
{
provide: STRIPE_PUBLISHABLE_KEY,
useFactory: () => environment.stripePublicKey
},
{
provide: TitleStrategy,
useClass: PageTitleStrategy

1
apps/client/tsconfig.app.json

@ -1,7 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"moduleResolution": "bundler",
"outDir": "../../dist/out-tsc",
"types": ["node"],
"typeRoots": ["../../node_modules/@types"],

2
apps/client/tsconfig.json

@ -20,6 +20,8 @@
"strictTemplates": false
},
"compilerOptions": {
"lib": ["dom", "es2022"],
"module": "preserve",
"target": "es2020"
}
}

3
apps/client/tsconfig.spec.json

@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"module": "preserve",
"isolatedModules": true,
"types": ["jest", "node"],
"target": "es2016"
},

6
libs/common/src/lib/interfaces/info-item.interface.ts

@ -13,8 +13,14 @@ export interface InfoItem {
globalPermissions: string[];
isDataGatheringEnabled?: string;
isReadOnlyMode?: boolean;
/** @deprecated */
platforms: Platform[];
statistics: Statistics;
/** @deprecated */
stripePublicKey?: string;
subscriptionOffer?: SubscriptionOffer;
}

3
libs/common/src/lib/interfaces/responses/create-stripe-checkout-session-response.interface.ts

@ -1,3 +1,6 @@
export interface CreateStripeCheckoutSessionResponse {
/** @deprecated */
sessionId: string;
sessionUrl: string;
}

2
libs/common/src/lib/routes/routes.ts

@ -5,7 +5,7 @@ import { InternalRoute } from './interfaces/internal-route.interface';
import { PublicRoute } from './interfaces/public-route.interface';
if (typeof window !== 'undefined') {
import('@angular/localize/init');
import('@angular/localize');
} else {
(global as any).$localize = (
messageParts: TemplateStringsArray,

6
libs/common/tsconfig.json

@ -9,5 +9,9 @@
{
"path": "./tsconfig.spec.json"
}
]
],
"compilerOptions": {
"module": "preserve",
"lib": ["dom", "es2022"]
}
}

3
libs/common/tsconfig.spec.json

@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"module": "preserve",
"isolatedModules": true,
"types": ["jest", "node"]
},
"include": [

4
libs/ui/src/lib/fire-calculator/fire-calculator.component.stories.ts

@ -44,8 +44,10 @@ type Story = StoryObj<GfFireCalculatorComponent>;
export const Simple: Story = {
args: {
annualInterestRate: 5,
currency: 'USD',
fireWealth: 50000,
locale: locale
locale: locale,
savingsRate: 1000
}
};

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

@ -77,16 +77,16 @@ import { FireCalculatorService } from './fire-calculator.service';
templateUrl: './fire-calculator.component.html'
})
export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
@Input() annualInterestRate: number;
@Input() annualInterestRate = 0;
@Input() colorScheme: ColorScheme;
@Input() currency: string;
@Input() deviceType: string;
@Input() fireWealth: number;
@Input() fireWealth = 0;
@Input() hasPermissionToUpdateUserSettings: boolean;
@Input() locale = getLocale();
@Input() projectedTotalAmount: number;
@Input() projectedTotalAmount = 0;
@Input() retirementDate: Date;
@Input() savingsRate: number;
@Input() savingsRate = 0;
@Output() annualInterestRateChanged = new EventEmitter<number>();
@Output() calculationCompleted =

2
libs/ui/src/lib/logo-carousel/logo-carousel.component.ts

@ -1,11 +1,9 @@
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'

3
libs/ui/tsconfig.json

@ -14,7 +14,8 @@
}
],
"compilerOptions": {
"moduleResolution": "bundler",
"lib": ["dom", "es2022"],
"module": "preserve",
"target": "es2020",
// TODO: Remove once solved in tsconfig.base.json
"strict": false,

2
libs/ui/tsconfig.lib.json

@ -7,7 +7,7 @@
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": ["dom", "es2018"]
"lib": ["dom", "es2022"]
},
"exclude": [
"src/test-setup.ts",

3
libs/ui/tsconfig.spec.json

@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"module": "preserve",
"isolatedModules": true,
"types": ["jest", "node"],
"target": "es2016"
},

4867
package-lock.json

File diff suppressed because it is too large

87
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.224.2",
"version": "2.227.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
@ -54,17 +54,17 @@
"workspace-generator": "nx workspace-generator"
},
"dependencies": {
"@angular/animations": "20.3.9",
"@angular/cdk": "20.2.9",
"@angular/common": "20.3.9",
"@angular/compiler": "20.3.9",
"@angular/core": "20.3.9",
"@angular/forms": "20.3.9",
"@angular/material": "20.2.9",
"@angular/platform-browser": "20.3.9",
"@angular/platform-browser-dynamic": "20.3.9",
"@angular/router": "20.3.9",
"@angular/service-worker": "20.3.9",
"@angular/animations": "21.0.6",
"@angular/cdk": "21.0.5",
"@angular/common": "21.0.6",
"@angular/compiler": "21.0.6",
"@angular/core": "21.0.6",
"@angular/forms": "21.0.6",
"@angular/material": "21.0.5",
"@angular/platform-browser": "21.0.6",
"@angular/platform-browser-dynamic": "21.0.6",
"@angular/router": "21.0.6",
"@angular/service-worker": "21.0.6",
"@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0",
"@internationalized/number": "3.6.5",
@ -85,7 +85,6 @@
"@prisma/client": "6.19.0",
"@simplewebauthn/browser": "13.1.0",
"@simplewebauthn/server": "13.1.1",
"@stripe/stripe-js": "7.9.0",
"ai": "4.3.16",
"alphavantage": "2.2.0",
"big.js": "7.0.1",
@ -98,7 +97,7 @@
"chartjs-plugin-datalabels": "2.2.0",
"cheerio": "1.0.0",
"class-transformer": "0.5.1",
"class-validator": "0.14.2",
"class-validator": "0.14.3",
"color": "5.0.3",
"countries-and-timezones": "3.8.0",
"countries-list": "3.2.0",
@ -114,13 +113,12 @@
"ionicons": "8.0.13",
"jsonpath": "1.1.1",
"lodash": "4.17.21",
"marked": "15.0.4",
"marked": "17.0.1",
"ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.2.1",
"ngx-device-detector": "10.1.0",
"ngx-markdown": "20.0.0",
"ngx-device-detector": "11.0.0",
"ngx-markdown": "21.0.1",
"ngx-skeleton-loader": "11.3.0",
"ngx-stripe": "20.7.0",
"open-color": "1.9.1",
"papaparse": "5.3.1",
"passport": "0.7.0",
@ -130,46 +128,47 @@
"passport-openidconnect": "0.1.2",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"stripe": "18.5.0",
"stripe": "20.1.0",
"svgmap": "2.14.0",
"tablemark": "4.1.0",
"twitter-api-v2": "1.27.0",
"yahoo-finance2": "3.10.2",
"zone.js": "0.15.1"
"yahoo-finance2": "3.11.2",
"zone.js": "0.16.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "20.3.9",
"@angular-devkit/core": "20.3.9",
"@angular-devkit/schematics": "20.3.9",
"@angular-eslint/eslint-plugin": "20.7.0",
"@angular-eslint/eslint-plugin-template": "20.7.0",
"@angular-eslint/template-parser": "20.7.0",
"@angular/cli": "20.3.9",
"@angular/compiler-cli": "20.3.9",
"@angular/language-service": "20.3.9",
"@angular/localize": "20.3.9",
"@angular/pwa": "20.3.9",
"@angular-devkit/build-angular": "21.0.4",
"@angular-devkit/core": "21.0.4",
"@angular-devkit/schematics": "21.0.4",
"@angular-eslint/eslint-plugin": "21.1.0",
"@angular-eslint/eslint-plugin-template": "21.1.0",
"@angular-eslint/template-parser": "21.1.0",
"@angular/cli": "21.0.4",
"@angular/compiler-cli": "21.0.6",
"@angular/language-service": "21.0.6",
"@angular/localize": "21.0.6",
"@angular/pwa": "21.0.4",
"@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.35.0",
"@nestjs/schematics": "11.0.9",
"@nestjs/testing": "11.1.8",
"@nx/angular": "22.1.3",
"@nx/eslint-plugin": "22.1.3",
"@nx/jest": "22.1.3",
"@nx/js": "22.1.3",
"@nx/module-federation": "22.1.3",
"@nx/nest": "22.1.3",
"@nx/node": "22.1.3",
"@nx/storybook": "22.1.3",
"@nx/web": "22.1.3",
"@nx/workspace": "22.1.3",
"@schematics/angular": "20.3.9",
"@nx/angular": "22.3.3",
"@nx/eslint-plugin": "22.3.3",
"@nx/jest": "22.3.3",
"@nx/js": "22.3.3",
"@nx/module-federation": "22.3.3",
"@nx/nest": "22.3.3",
"@nx/node": "22.3.3",
"@nx/storybook": "22.3.3",
"@nx/web": "22.3.3",
"@nx/workspace": "22.3.3",
"@schematics/angular": "21.0.4",
"@storybook/addon-docs": "10.1.10",
"@storybook/angular": "10.1.10",
"@trivago/prettier-plugin-sort-imports": "5.2.2",
"@types/big.js": "6.2.2",
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "30.0.0",
"@types/jsonpath": "0.2.4",
"@types/lodash": "4.17.20",
"@types/node": "22.15.17",
"@types/papaparse": "5.3.7",
@ -185,7 +184,7 @@
"jest": "30.2.0",
"jest-environment-jsdom": "30.2.0",
"jest-preset-angular": "16.0.0",
"nx": "22.1.3",
"nx": "22.3.3",
"prettier": "3.7.4",
"prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.19.0",

3
tsconfig.base.json

@ -4,8 +4,9 @@
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"moduleResolution": "bundler",
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",

Loading…
Cancel
Save