Browse Source

Merge branch 'main' into feature/include-cash-in-portfolio-performance

pull/7148/head
Thomas Kaul 6 days ago
committed by GitHub
parent
commit
ce6e796f4e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      CHANGELOG.md
  2. 9
      apps/api/src/app/logo/get-logo.dto.ts
  3. 3
      apps/api/src/app/logo/logo.controller.ts
  4. 2
      apps/api/src/app/logo/logo.service.ts
  5. 34
      apps/api/src/middlewares/html-template.middleware.ts
  6. 2
      apps/api/src/services/data-provider/data-provider.service.ts
  7. 14
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  8. 2
      apps/client/src/app/components/admin-settings/admin-settings.component.html
  9. 12
      apps/client/src/app/components/admin-settings/admin-settings.component.ts
  10. 18
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  11. 5
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
  12. 10
      apps/client/src/app/components/user-account-membership/user-account-membership.component.ts
  13. 5
      apps/client/src/app/components/user-account-membership/user-account-membership.html
  14. 13
      apps/client/src/app/components/user-account-settings/user-account-settings.component.ts
  15. 6
      apps/client/src/app/components/user-account-settings/user-account-settings.html
  16. 3
      apps/client/src/app/pages/about/changelog/changelog-page.component.ts
  17. 3
      apps/client/src/app/pages/about/license/license-page.component.ts
  18. 3
      apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts
  19. 4
      apps/client/src/app/pages/about/overview/about-overview-page.component.ts
  20. 3
      apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts
  21. 3
      apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts
  22. 4
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  23. 9
      apps/client/src/app/pages/api/api-page.component.ts
  24. 8
      apps/client/src/app/pages/auth/auth-page.component.ts
  25. 3
      apps/client/src/app/pages/demo/demo-page.component.ts
  26. 4
      apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts
  27. 4
      apps/client/src/app/pages/faq/saas/saas-page.component.ts
  28. 7
      apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts
  29. 10
      apps/client/src/app/pages/features/features-page.component.ts
  30. 3
      apps/client/src/app/pages/i18n/i18n-page.component.ts
  31. 3
      apps/client/src/app/pages/landing/landing-page.component.ts
  32. 3
      apps/client/src/app/pages/markets/markets-page.component.ts
  33. 4
      apps/client/src/app/pages/open/open-page.component.ts
  34. 2
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  35. 4
      apps/client/src/app/pages/pricing/pricing-page.component.ts
  36. 2
      apps/client/src/app/pages/public/public-page.component.ts
  37. 2
      apps/client/src/app/pages/register/register-page.component.ts
  38. 3
      apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts
  39. 3
      apps/client/src/app/pages/resources/guides/resources-guides.component.ts
  40. 3
      apps/client/src/app/pages/resources/markets/resources-markets.component.ts
  41. 3
      apps/client/src/app/pages/resources/overview/resources-overview.component.ts
  42. 3
      apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts
  43. 4
      apps/client/src/app/pages/webauthn/webauthn-page.component.ts
  44. 2
      libs/common/src/lib/config.ts
  45. 2
      libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts
  46. 4
      package-lock.json
  47. 2
      package.json

25
CHANGELOG.md

@ -11,6 +11,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Included cash in the performance calculation of the portfolio on the home page - Included cash in the performance calculation of the portfolio on the home page
## 3.27.0 - 2026-07-15
### Changed
- Hardened the validation of the URL in the logo endpoint
- Set the change detection strategy to `OnPush` in the about pages
- Set the change detection strategy to `OnPush` in the accounts page
- Set the change detection strategy to `OnPush` in the demo page
- Set the change detection strategy to `OnPush` in the features page
- Set the change detection strategy to `OnPush` in the Frequently Asked Questions (FAQ) pages
- Set the change detection strategy to `OnPush` in the landing page
- Set the change detection strategy to `OnPush` in the markets page
- Set the change detection strategy to `OnPush` in the _Open Startup_ (`/open`) page
- Set the change detection strategy to `OnPush` in the pricing page
- Set the change detection strategy to `OnPush` in the public page
- Set the change detection strategy to `OnPush` in the registration page
- Set the change detection strategy to `OnPush` in the resources pages
### Fixed
- Fixed an issue where the symbol was not selected when cloning an activity
- Resolved a startup error in data gathering caused by uninitialized data provider mappings
- Improved the error handling in the `HtmlTemplateMiddleware`
- Improved the error handling in the get quotes functionality of the _Financial Modeling Prep_ service
## 3.26.0 - 2026-07-14 ## 3.26.0 - 2026-07-14
### Added ### Added

9
apps/api/src/app/logo/get-logo.dto.ts

@ -0,0 +1,9 @@
import { IsUrl } from 'class-validator';
export class GetLogoDto {
@IsUrl({
protocols: ['http', 'https'],
require_protocol: true
})
url: string;
}

3
apps/api/src/app/logo/logo.controller.ts

@ -12,6 +12,7 @@ import {
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { Response } from 'express'; import { Response } from 'express';
import { GetLogoDto } from './get-logo.dto';
import { LogoService } from './logo.service'; import { LogoService } from './logo.service';
@Controller('logo') @Controller('logo')
@ -41,7 +42,7 @@ export class LogoController {
@Get() @Get()
public async getLogoByUrl( public async getLogoByUrl(
@Query('url') url: string, @Query() { url }: GetLogoDto,
@Res() response: Response @Res() response: Response
) { ) {
try { try {

2
apps/api/src/app/logo/logo.service.ts

@ -47,7 +47,7 @@ export class LogoService {
private async getBuffer(aUrl: string) { private async getBuffer(aUrl: string) {
const blob = await this.fetchService const blob = await this.fetchService
.fetch( .fetch(
`https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`, `https://t0.gstatic.com/faviconV2?client=SOCIAL&fallback_opts=TYPE,SIZE,URL&size=64&type=FAVICON&url=${encodeURIComponent(aUrl)}`,
{ {
headers: { 'User-Agent': 'request' }, headers: { 'User-Agent': 'request' },
signal: AbortSignal.timeout( signal: AbortSignal.timeout(

34
apps/api/src/middlewares/html-template.middleware.ts

@ -97,20 +97,29 @@ export class HtmlTemplateMiddleware implements NestMiddleware {
private indexHtmlMap: { [languageCode: string]: string } = {}; private indexHtmlMap: { [languageCode: string]: string } = {};
public constructor(private readonly i18nService: I18nService) { public constructor(private readonly i18nService: I18nService) {
if (!environment.production) {
return;
}
this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce((map, languageCode) => {
const indexHtmlPath = join(
__dirname,
'..',
'client',
languageCode,
'index.html'
);
try { try {
this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce( map[languageCode] = readFileSync(indexHtmlPath, 'utf8');
(map, languageCode) => ({ } catch {
...map, this.logger.warn(
[languageCode]: readFileSync( `Skipping language '${languageCode}': ${indexHtmlPath} not found`
join(__dirname, '..', 'client', languageCode, 'index.html'),
'utf8'
)
}),
{}
); );
} catch (error) {
this.logger.error('Failed to initialize index HTML map', error);
} }
return map;
}, {});
} }
public use(request: Request, response: Response, next: NextFunction) { public use(request: Request, response: Response, next: NextFunction) {
@ -118,7 +127,8 @@ export class HtmlTemplateMiddleware implements NestMiddleware {
let languageCode = path.substr(1, 2); let languageCode = path.substr(1, 2);
if ( if (
!(SUPPORTED_LANGUAGE_CODES as readonly string[]).includes(languageCode) !(SUPPORTED_LANGUAGE_CODES as readonly string[]).includes(languageCode) ||
!this.indexHtmlMap[languageCode]
) { ) {
languageCode = DEFAULT_LANGUAGE_CODE; languageCode = DEFAULT_LANGUAGE_CODE;
} }

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

@ -44,7 +44,7 @@ import { AssetProfileInvalidError } from './errors/asset-profile-invalid.error';
export class DataProviderService implements OnModuleInit { export class DataProviderService implements OnModuleInit {
private readonly logger = new Logger(DataProviderService.name); private readonly logger = new Logger(DataProviderService.name);
private dataProviderMapping: { [dataProviderName: string]: string }; private dataProviderMapping: { [dataProviderName: string]: string } = {};
public constructor( public constructor(
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,

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

@ -47,7 +47,7 @@ import {
isSameDay, isSameDay,
parseISO parseISO
} from 'date-fns'; } from 'date-fns';
import { uniqBy } from 'lodash'; import { isArray, uniqBy } from 'lodash';
@Injectable() @Injectable()
export class FinancialModelingPrepService export class FinancialModelingPrepService
@ -440,10 +440,14 @@ export class FinancialModelingPrepService
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
) )
.then( .then(async (res) => {
(res) => const json = (await res.json()) as unknown as {
res.json() as unknown as { price: number; symbol: string }[] price: number;
) symbol: string;
}[];
return isArray(json) ? json : [];
})
]); ]);
for (const { currency, symbolTarget } of assetProfileResolutions) { for (const { currency, symbolTarget } of assetProfileResolutions) {

2
apps/client/src/app/components/admin-settings/admin-settings.component.html

@ -21,8 +21,8 @@
<mat-card-actions class="pb-3 pt-0 px-3"> <mat-card-actions class="pb-3 pt-0 px-3">
<a <a
class="special" class="special"
href="mailto:hi@ghostfol.io?subject=Ghostfolio Premium Data Provider&body=Hello,%0D%0DI am interested in the Ghostfolio Premium data provider. Could you please give me access so I can try it for some time?%0D%0DKind regards"
mat-flat-button mat-flat-button
[href]="premiumDataProviderMailHref"
> >
<ng-container i18n>Get Access</ng-container> <ng-container i18n>Get Access</ng-container>
</a> </a>

12
apps/client/src/app/components/admin-settings/admin-settings.component.ts

@ -2,7 +2,10 @@ import { GfAdminPlatformComponent } from '@ghostfolio/client/components/admin-pl
import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component'; import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component';
import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; import {
E_MAIL_LINE_BREAK,
PROPERTY_API_KEY_GHOSTFOLIO
} from '@ghostfolio/common/config';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { import {
@ -80,6 +83,13 @@ export class GfAdminSettingsComponent implements OnInit {
public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse; public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse;
public isGhostfolioApiKeyValid: boolean; public isGhostfolioApiKeyValid: boolean;
public isLoading = false; public isLoading = false;
public readonly premiumDataProviderMailHref = `mailto:hi@ghostfol.io?subject=Ghostfolio Premium Data Provider&body=${[
'Hello,',
'',
'I am interested in the Ghostfolio Premium data provider. Could you please give me access so I can try it for some time?',
'',
'Kind regards'
].join(E_MAIL_LINE_BREAK)}`;
public pricingUrl: string; public pricingUrl: string;
public user: User; public user: User;

18
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts

@ -1,6 +1,7 @@
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import {
DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE,
E_MAIL_LINE_BREAK,
NUMERICAL_PRECISION_THRESHOLD_3_FIGURES, NUMERICAL_PRECISION_THRESHOLD_3_FIGURES,
NUMERICAL_PRECISION_THRESHOLD_4_FIGURES NUMERICAL_PRECISION_THRESHOLD_4_FIGURES
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
@ -169,7 +170,7 @@ export class GfHoldingDetailDialogComponent implements OnInit {
protected readonly pageSize = DEFAULT_PAGE_SIZE; protected readonly pageSize = DEFAULT_PAGE_SIZE;
protected quantity: number; protected quantity: number;
protected quantityPrecision = 2; protected quantityPrecision = 2;
protected reportDataGlitchMail: string; protected reportDataGlitchMailHref: string;
protected readonly round = round; protected readonly round = round;
protected readonly routerLinkAdminControlMarketData = protected readonly routerLinkAdminControlMarketData =
internalRoutes.adminControl.subRoutes.marketData.routerLink; internalRoutes.adminControl.subRoutes.marketData.routerLink;
@ -456,7 +457,20 @@ export class GfHoldingDetailDialogComponent implements OnInit {
this.assetProfile?.symbol ? ` (${this.assetProfile.symbol})` : '' this.assetProfile?.symbol ? ` (${this.assetProfile.symbol})` : ''
}`; }`;
this.reportDataGlitchMail = `mailto:hi@ghostfol.io?subject=${reportDataGlitchSubject}&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${this.assetProfile?.symbol}%0DData Source: ${this.assetProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; this.reportDataGlitchMailHref = `mailto:hi@ghostfol.io?subject=${reportDataGlitchSubject}&body=${[
'Hello',
'',
'I would like to report a data glitch for',
'',
`Symbol: ${this.assetProfile?.symbol}`,
`Data Source: ${this.assetProfile?.dataSource}`,
'',
'Additional notes:',
'',
'Can you please take a look?',
'',
'Kind regards'
].join(E_MAIL_LINE_BREAK)}`;
if (this.assetProfile?.assetClass) { if (this.assetProfile?.assetClass) {
this.assetClass = translate(this.assetProfile?.assetClass); this.assetClass = translate(this.assetProfile?.assetClass);

5
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html

@ -456,7 +456,10 @@
dataSource?.data.length > 0 && dataSource?.data.length > 0 &&
data.hasPermissionToReportDataGlitch === true data.hasPermissionToReportDataGlitch === true
) { ) {
<a color="warn" mat-stroked-button [href]="reportDataGlitchMail" <a
color="warn"
mat-stroked-button
[href]="reportDataGlitchMailHref"
><ion-icon class="mr-1" name="flag-outline" /><span i18n ><ion-icon class="mr-1" name="flag-outline" /><span i18n
>Report Data Glitch</span >Report Data Glitch</span
>...</a >...</a

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

@ -1,4 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { E_MAIL_LINE_BREAK } from '@ghostfolio/common/config';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { getDateFormatString } from '@ghostfolio/common/helper';
import { User } from '@ghostfolio/common/interfaces'; import { User } from '@ghostfolio/common/interfaces';
@ -48,8 +49,13 @@ export class GfUserAccountMembershipComponent {
protected hasPermissionToCreateApiKey: boolean; protected hasPermissionToCreateApiKey: boolean;
protected hasPermissionToUpdateUserSettings: boolean; protected hasPermissionToUpdateUserSettings: boolean;
protected price: number; protected price: number;
protected readonly trySubscriptionMail = protected readonly trySubscriptionMailHref = `mailto:hi@ghostfol.io?subject=Ghostfolio Premium Trial&body=${[
'mailto:hi@ghostfol.io?subject=Ghostfolio Premium Trial&body=Hello%0D%0DI am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?%0D%0DKind regards'; 'Hello',
'',
'I am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?',
'',
'Kind regards'
].join(E_MAIL_LINE_BREAK)}`;
protected user: User; protected user: User;
private couponId: string | undefined; private couponId: string | undefined;

5
apps/client/src/app/components/user-account-membership/user-account-membership.html

@ -49,7 +49,10 @@
} }
<div class="align-items-center d-flex justify-content-center mt-4"> <div class="align-items-center d-flex justify-content-center mt-4">
@if (!user?.subscription?.expiresAt) { @if (!user?.subscription?.expiresAt) {
<a class="mx-1" mat-stroked-button [href]="trySubscriptionMail" <a
class="mx-1"
mat-stroked-button
[href]="trySubscriptionMailHref"
><span i18n>Try Premium</span> ><span i18n>Try Premium</span>
<gf-premium-indicator <gf-premium-indicator
class="d-inline-block ml-1" class="d-inline-block ml-1"

13
apps/client/src/app/components/user-account-settings/user-account-settings.component.ts

@ -5,6 +5,7 @@ import {
} from '@ghostfolio/client/services/settings-storage.service'; } from '@ghostfolio/client/services/settings-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
import { E_MAIL_LINE_BREAK } from '@ghostfolio/common/config';
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { downloadAsFile } from '@ghostfolio/common/helper'; import { downloadAsFile } from '@ghostfolio/common/helper';
import { User } from '@ghostfolio/common/interfaces'; import { User } from '@ghostfolio/common/interfaces';
@ -72,7 +73,7 @@ import { catchError } from 'rxjs/operators';
export class GfUserAccountSettingsComponent implements OnInit { export class GfUserAccountSettingsComponent implements OnInit {
protected readonly appearancePlaceholder = $localize`Auto`; protected readonly appearancePlaceholder = $localize`Auto`;
protected readonly baseCurrency: string; protected readonly baseCurrency: string;
protected closeUserAccountMail: string; protected closeUserAccountMailHref: string;
protected readonly currencies: string[] = []; protected readonly currencies: string[] = [];
protected readonly deleteOwnUserForm = inject(NonNullableFormBuilder).group({ protected readonly deleteOwnUserForm = inject(NonNullableFormBuilder).group({
accessToken: ['', Validators.required] accessToken: ['', Validators.required]
@ -126,7 +127,15 @@ export class GfUserAccountSettingsComponent implements OnInit {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
this.closeUserAccountMail = `mailto:hi@ghostfol.io?subject=Delete Account&body=Hello%0D%0DPlease delete my Ghostfolio account.%0D%0DUser ID: ${this.user.id}%0D%0DKind regards`; this.closeUserAccountMailHref = `mailto:hi@ghostfol.io?subject=Delete Account&body=${[
'Hello',
'',
'Please delete my Ghostfolio account.',
'',
`User ID: ${this.user.id}`,
'',
'Kind regards'
].join(E_MAIL_LINE_BREAK)}`;
this.hasPermissionToDeleteOwnUser = hasPermission( this.hasPermissionToDeleteOwnUser = hasPermission(
this.user.permissions, this.user.permissions,

6
apps/client/src/app/components/user-account-settings/user-account-settings.html

@ -349,7 +349,11 @@
first before your Ghostfolio account can be closed. first before your Ghostfolio account can be closed.
</div> </div>
} @else if (hasPermissionToRequestOwnUserDeletion) { } @else if (hasPermissionToRequestOwnUserDeletion) {
<a color="warn" i18n mat-flat-button [href]="closeUserAccountMail" <a
color="warn"
i18n
mat-flat-button
[href]="closeUserAccountMailHref"
>Close Account</a >Close Account</a
> >
} }

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

@ -1,8 +1,9 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MarkdownModule } from 'ngx-markdown'; import { MarkdownModule } from 'ngx-markdown';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarkdownModule, NgxSkeletonLoaderModule], imports: [MarkdownModule, NgxSkeletonLoaderModule],
selector: 'gf-changelog-page', selector: 'gf-changelog-page',
styleUrls: ['./changelog-page.scss'], styleUrls: ['./changelog-page.scss'],

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

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MarkdownModule } from 'ngx-markdown'; import { MarkdownModule } from 'ngx-markdown';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarkdownModule], imports: [MarkdownModule],
selector: 'gf-license-page', selector: 'gf-license-page',
styleUrls: ['./license-page.scss'], styleUrls: ['./license-page.scss'],

3
apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
@ -8,6 +8,7 @@ import { arrowForwardOutline } from 'ionicons/icons';
const ossFriends = require('../../../../assets/oss-friends.json'); const ossFriends = require('../../../../assets/oss-friends.json');
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [IonIcon, MatButtonModule, MatCardModule], imports: [IonIcon, MatButtonModule, MatCardModule],
selector: 'gf-oss-friends-page', selector: 'gf-oss-friends-page',
styleUrls: ['./oss-friends-page.scss'], styleUrls: ['./oss-friends-page.scss'],

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

@ -5,6 +5,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
@ -25,6 +26,7 @@ import {
} from 'ionicons/icons'; } from 'ionicons/icons';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [IonIcon, MatButtonModule, RouterModule], imports: [IonIcon, MatButtonModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-about-overview-page', selector: 'gf-about-overview-page',
@ -68,9 +70,9 @@ export class GfAboutOverviewPageComponent implements OnInit {
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
} }
} }

3
apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MarkdownModule } from 'ngx-markdown'; import { MarkdownModule } from 'ngx-markdown';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarkdownModule], imports: [MarkdownModule],
selector: 'gf-privacy-policy-page', selector: 'gf-privacy-policy-page',
styleUrls: ['./privacy-policy-page.scss'], styleUrls: ['./privacy-policy-page.scss'],

3
apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MarkdownModule } from 'ngx-markdown'; import { MarkdownModule } from 'ngx-markdown';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MarkdownModule], imports: [MarkdownModule],
selector: 'gf-terms-of-service-page', selector: 'gf-terms-of-service-page',
styleUrls: ['./terms-of-service-page.scss'], styleUrls: ['./terms-of-service-page.scss'],

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

@ -15,6 +15,7 @@ import { NotificationService } from '@ghostfolio/ui/notifications';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
computed, computed,
@ -36,6 +37,7 @@ import { TransferBalanceDialogParams } from './transfer-balance/interfaces/inter
import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-balance-dialog.component'; import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-balance-dialog.component';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfAccountsTableComponent, GfFabComponent, RouterModule], imports: [GfAccountsTableComponent, GfFabComponent, RouterModule],
selector: 'gf-accounts-page', selector: 'gf-accounts-page',
@ -120,9 +122,9 @@ export class GfAccountsPageComponent implements OnInit {
this.user.permissions, this.user.permissions,
permissions.updateAccount permissions.updateAccount
); );
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
this.fetchAccounts(); this.fetchAccounts();

9
apps/client/src/app/pages/api/api-page.component.ts

@ -22,7 +22,13 @@ import {
HttpHeaders, HttpHeaders,
HttpParams HttpParams
} from '@angular/common/http'; } from '@angular/common/http';
import { Component, DestroyRef, inject, OnInit } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
DestroyRef,
inject,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { format, startOfYear } from 'date-fns'; import { format, startOfYear } from 'date-fns';
@ -33,6 +39,7 @@ import { catchError, map, Observable, of, OperatorFunction } from 'rxjs';
import { FetchFailure, FetchResult } from './interfaces/interfaces'; import { FetchFailure, FetchResult } from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [
CommonModule, CommonModule,

8
apps/client/src/app/pages/auth/auth-page.component.ts

@ -4,11 +4,17 @@ import {
} from '@ghostfolio/client/services/settings-storage.service'; } from '@ghostfolio/client/services/settings-storage.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { Component, DestroyRef, OnInit } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
DestroyRef,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-auth-page', selector: 'gf-auth-page',
styleUrls: ['./auth-page.scss'], styleUrls: ['./auth-page.scss'],
templateUrl: './auth-page.html' templateUrl: './auth-page.html'

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

@ -3,10 +3,11 @@ import { InfoItem } from '@ghostfolio/common/interfaces';
import { NotificationService } from '@ghostfolio/ui/notifications'; import { NotificationService } from '@ghostfolio/ui/notifications';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
selector: 'gf-demo-page', selector: 'gf-demo-page',
standalone: true, standalone: true,

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

@ -4,6 +4,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
@ -14,6 +15,7 @@ import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule], imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@ -38,9 +40,9 @@ export class GfFaqOverviewPageComponent {
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
} }
} }

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

@ -4,6 +4,7 @@ import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
@ -14,6 +15,7 @@ import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule], imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@ -42,9 +44,9 @@ export class GfSaasPageComponent {
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
} }
} }

7
apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts

@ -1,11 +1,16 @@
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA
} from '@angular/core';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule], imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],

10
apps/client/src/app/pages/features/features-page.component.ts

@ -5,13 +5,19 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
@ -46,9 +52,9 @@ export class GfFeaturesPageComponent {
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
this.hasPermissionForSubscription = hasPermission( this.hasPermissionForSubscription = hasPermission(

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

@ -1,6 +1,7 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
selector: 'gf-i18n-page', selector: 'gf-i18n-page',
standalone: true, standalone: true,

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

@ -8,7 +8,7 @@ import { DataService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart';
import { Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@ -22,6 +22,7 @@ import {
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [
GfCarouselComponent, GfCarouselComponent,

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

@ -1,8 +1,9 @@
import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfHomeMarketComponent], imports: [GfHomeMarketComponent],
selector: 'gf-markets-page', selector: 'gf-markets-page',

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

@ -4,6 +4,7 @@ import { DataService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
@ -14,6 +15,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfValueComponent, MatCardModule], imports: [GfValueComponent, MatCardModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
@ -42,9 +44,9 @@ export class GfOpenPageComponent implements OnInit {
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
} }
} }

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

@ -409,7 +409,7 @@ export class GfActivitiesPageComponent implements OnInit {
activity: { activity: {
...aActivity, ...aActivity,
accountId: aActivity?.accountId, accountId: aActivity?.accountId,
assetProfile: null, assetProfile: aActivity?.assetProfile ?? null,
date: new Date(), date: new Date(),
id: null, id: null,
fee: 0, fee: 0,

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

@ -8,6 +8,7 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
@ -32,6 +33,7 @@ import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
@ -123,9 +125,9 @@ export class GfPricingPageComponent implements OnInit {
this.label = this.user?.subscription?.offer?.label; this.label = this.user?.subscription?.offer?.label;
this.price = this.user?.subscription?.offer?.price; this.price = this.user?.subscription?.offer?.price;
this.priceId = this.user?.subscription?.offer?.priceId; this.priceId = this.user?.subscription?.offer?.priceId;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}
}); });
} }

2
apps/client/src/app/pages/public/public-page.component.ts

@ -17,6 +17,7 @@ import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
computed, computed,
@ -38,6 +39,7 @@ import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [
GfActivitiesTableComponent, GfActivitiesTableComponent,

2
apps/client/src/app/pages/register/register-page.component.ts

@ -6,6 +6,7 @@ import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { import {
ChangeDetectionStrategy,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
DestroyRef, DestroyRef,
@ -21,6 +22,7 @@ import { UserAccountRegistrationDialogParams } from './user-account-registration
import { GfUserAccountRegistrationDialogComponent } from './user-account-registration-dialog/user-account-registration-dialog.component'; import { GfUserAccountRegistrationDialogComponent } from './user-account-registration-dialog/user-account-registration-dialog.component';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [GfLogoComponent, MatButtonModule, RouterModule], imports: [GfLogoComponent, MatButtonModule, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],

3
apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts

@ -3,10 +3,11 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { DataService } from '@ghostfolio/ui/services'; import { DataService } from '@ghostfolio/ui/services';
import { Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterModule], imports: [RouterModule],
selector: 'gf-resources-glossary', selector: 'gf-resources-glossary',
styleUrls: ['./resources-glossary.component.scss'], styleUrls: ['./resources-glossary.component.scss'],

3
apps/client/src/app/pages/resources/guides/resources-guides.component.ts

@ -1,7 +1,8 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterModule], imports: [RouterModule],
selector: 'gf-resources-guides', selector: 'gf-resources-guides',
styleUrls: ['./resources-guides.component.scss'], styleUrls: ['./resources-guides.component.scss'],

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

@ -1,6 +1,7 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-resources-markets', selector: 'gf-resources-markets',
styleUrls: ['./resources-markets.component.scss'], styleUrls: ['./resources-markets.component.scss'],
templateUrl: './resources-markets.component.html' templateUrl: './resources-markets.component.html'

3
apps/client/src/app/pages/resources/overview/resources-overview.component.ts

@ -1,9 +1,10 @@
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterModule], imports: [RouterModule],
selector: 'gf-resources-overview', selector: 'gf-resources-overview',
styleUrls: ['./resources-overview.component.scss'], styleUrls: ['./resources-overview.component.scss'],

3
apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts

@ -1,7 +1,7 @@
import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools';
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
@ -9,6 +9,7 @@ import { addIcons } from 'ionicons';
import { chevronForwardOutline } from 'ionicons/icons'; import { chevronForwardOutline } from 'ionicons/icons';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [IonIcon, MatCardModule, RouterModule], imports: [IonIcon, MatCardModule, RouterModule],
selector: 'gf-personal-finance-tools-page', selector: 'gf-personal-finance-tools-page',

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

@ -2,6 +2,7 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.s
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
DestroyRef, DestroyRef,
@ -13,6 +14,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' }, host: { class: 'page' },
imports: [MatButtonModule, MatProgressSpinnerModule], imports: [MatButtonModule, MatProgressSpinnerModule],
selector: 'gf-webauthn-page', selector: 'gf-webauthn-page',
@ -56,7 +58,9 @@ export class GfWebauthnPageComponent implements OnInit {
}, },
(error) => { (error) => {
console.error(error); console.error(error);
this.hasError = true; this.hasError = true;
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }
); );

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

@ -184,6 +184,8 @@ export const DERIVED_CURRENCIES = [
} }
]; ];
export const E_MAIL_LINE_BREAK = '%0D%0A';
export const GATHER_ASSET_PROFILE_PROCESS_JOB_NAME = 'GATHER_ASSET_PROFILE'; export const GATHER_ASSET_PROFILE_PROCESS_JOB_NAME = 'GATHER_ASSET_PROFILE';
export const GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS: JobOptions = { export const GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS: JobOptions = {
attempts: 12, attempts: 12,

2
libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts

@ -15,6 +15,6 @@ export class EntityLogoImageSourceService {
} }
public getLogoUrlByUrl(url: string) { public getLogoUrlByUrl(url: string) {
return `../api/v1/logo?url=${url}`; return `../api/v1/logo?url=${encodeURIComponent(url)}`;
} }
} }

4
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.26.0", "version": "3.27.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.26.0", "version": "3.27.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.26.0", "version": "3.27.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",

Loading…
Cancel
Save