Browse Source

Merge remote-tracking branch 'origin/main' into task/migrate-tags-selector-component

pull/5850/head
KenTandrian 5 days ago
parent
commit
898c3ead78
  1. 6
      CHANGELOG.md
  2. 13
      README.md
  3. 79
      apps/api/src/app/endpoints/ai/ai.service.ts
  4. 7
      apps/client/src/app/app.component.ts
  5. 27
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  6. 62
      apps/client/src/app/components/admin-platform/admin-platform.component.ts
  7. 14
      apps/client/src/app/components/admin-tag/admin-tag.component.ts
  8. 2
      apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts
  9. 8
      apps/client/src/app/components/admin-users/admin-users.component.ts
  10. 6
      apps/client/src/app/components/header/header.component.ts
  11. 20
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  12. 5
      apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts
  13. 4
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts
  14. 7
      apps/client/src/app/components/rule/rule.component.ts
  15. 15
      apps/client/src/app/components/user-account-access/user-account-access.component.ts
  16. 24
      apps/client/src/app/pages/about/overview/about-overview-page.html
  17. 25
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  18. 2
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts
  19. 73
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  20. 1
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts
  21. 2
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts
  22. 7
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  23. 22
      apps/client/src/app/pages/register/register-page.component.ts
  24. 18
      apps/client/src/app/pages/resources/glossary/resources-glossary.component.html
  25. 4
      apps/client/src/app/pages/resources/guides/resources-guides.component.html
  26. 8
      apps/client/src/app/pages/resources/markets/resources-markets.component.html
  27. 2
      apps/client/src/app/pages/resources/overview/resources-overview.component.html
  28. 22
      apps/client/src/app/services/user/user.service.ts
  29. 7
      libs/ui/src/lib/benchmark/benchmark.component.ts
  30. 28
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
  31. 4
      package-lock.json
  32. 2
      package.json

6
CHANGELOG.md

@ -5,15 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## 2.212.0 - 2025-10-29
### Added ### Added
- Added a close holding button to the holding detail dialog - Added a close holding button to the holding detail dialog
- Added the _Sponsors_ section to the about page
- Extended the user detail dialog in the users section of the admin control panel - Extended the user detail dialog in the users section of the admin control panel
### Changed ### Changed
- Refactored the generation of the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental)
- Refactored the generation of the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental)
- Improved the usability of the user detail dialog in the users section of the admin control panel
- Improved the language localization for German (`de`) - Improved the language localization for German (`de`)
### Fixed ### Fixed

13
README.md

@ -297,7 +297,18 @@ Ghostfolio is **100% free** and **open source**. We encourage and support an act
Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22%20no%3Aassignee), even for [newcomers](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%20no%3Aassignee). Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://x.com/ghostfolio_) on _X_. We would love to hear from you. Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22%20no%3Aassignee), even for [newcomers](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%20no%3Aassignee). Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://x.com/ghostfolio_) on _X_. We would love to hear from you.
If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio). If you like to support this project, become a [**Sponsor**](https://github.com/sponsors/ghostfolio), get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio).
## Sponsors
<div align="center">
<p>
Browser testing via<br />
<a href="https://www.lambdatest.com?utm_medium=sponsor&utm_source=ghostfolio" target="_blank" title="LambdaTest - AI Powered Testing Tool">
<img alt="LambdaTest Logo" height="45" width="250" src="https://www.lambdatest.com/blue-logo.png" />
</a>
</p>
</div>
## Analytics ## Analytics

79
apps/api/src/app/endpoints/ai/ai.service.ts

@ -14,6 +14,27 @@ import type { ColumnDescriptor } from 'tablemark';
@Injectable() @Injectable()
export class AiService { export class AiService {
private static readonly HOLDINGS_TABLE_COLUMN_DEFINITIONS: ({
key:
| 'ALLOCATION_PERCENTAGE'
| 'ASSET_CLASS'
| 'ASSET_SUB_CLASS'
| 'CURRENCY'
| 'NAME'
| 'SYMBOL';
} & ColumnDescriptor)[] = [
{ key: 'NAME', name: 'Name' },
{ key: 'SYMBOL', name: 'Symbol' },
{ key: 'CURRENCY', name: 'Currency' },
{ key: 'ASSET_CLASS', name: 'Asset Class' },
{ key: 'ASSET_SUB_CLASS', name: 'Asset Sub Class' },
{
align: 'right',
key: 'ALLOCATION_PERCENTAGE',
name: 'Allocation in Percentage'
}
];
public constructor( public constructor(
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
private readonly propertyService: PropertyService private readonly propertyService: PropertyService
@ -59,14 +80,10 @@ export class AiService {
userId userId
}); });
const holdingsTableColumns: ColumnDescriptor[] = [ const holdingsTableColumns: ColumnDescriptor[] =
{ name: 'Name' }, AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.map(({ align, name }) => {
{ name: 'Symbol' }, return { name, align: align ?? 'left' };
{ name: 'Currency' }, });
{ name: 'Asset Class' },
{ name: 'Asset Sub Class' },
{ align: 'right', name: 'Allocation in Percentage' }
];
const holdingsTableRows = Object.values(holdings) const holdingsTableRows = Object.values(holdings)
.sort((a, b) => { .sort((a, b) => {
@ -78,17 +95,45 @@ export class AiService {
assetClass, assetClass,
assetSubClass, assetSubClass,
currency, currency,
name, name: label,
symbol symbol
}) => { }) => {
return { return AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.reduce(
Name: name, (row, { key, name }) => {
Symbol: symbol, switch (key) {
Currency: currency, case 'ALLOCATION_PERCENTAGE':
'Asset Class': assetClass ?? '', row[name] = `${(allocationInPercentage * 100).toFixed(3)}%`;
'Asset Sub Class': assetSubClass ?? '', break;
'Allocation in Percentage': `${(allocationInPercentage * 100).toFixed(3)}%`
}; case 'ASSET_CLASS':
row[name] = assetClass ?? '';
break;
case 'ASSET_SUB_CLASS':
row[name] = assetSubClass ?? '';
break;
case 'CURRENCY':
row[name] = currency;
break;
case 'NAME':
row[name] = label;
break;
case 'SYMBOL':
row[name] = symbol;
break;
default:
row[name] = '';
break;
}
return row;
},
{} as Record<string, string>
);
} }
); );

7
apps/client/src/app/app.component.ts

@ -276,7 +276,10 @@ export class AppComponent implements OnDestroy, OnInit {
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, { const dialogRef = this.dialog.open<
GfHoldingDetailDialogComponent,
HoldingDetailDialogParams
>(GfHoldingDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
dataSource, dataSource,
@ -302,7 +305,7 @@ export class AppComponent implements OnDestroy, OnInit {
hasPermission(this.user?.permissions, permissions.updateOrder) && hasPermission(this.user?.permissions, permissions.updateOrder) &&
!this.user?.settings?.isRestrictedView, !this.user?.settings?.isRestrictedView,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
} as HoldingDetailDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

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

@ -430,7 +430,10 @@ export class GfAdminMarketDataComponent
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
const dialogRef = this.dialog.open(GfAssetProfileDialogComponent, { const dialogRef = this.dialog.open<
GfAssetProfileDialogComponent,
AssetProfileDialogParams
>(GfAssetProfileDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
dataSource, dataSource,
@ -438,7 +441,7 @@ export class GfAdminMarketDataComponent
colorScheme: this.user?.settings.colorScheme, colorScheme: this.user?.settings.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
} as AssetProfileDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -465,17 +468,17 @@ export class GfAdminMarketDataComponent
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfCreateAssetProfileDialogComponent, GfCreateAssetProfileDialogComponent,
{ CreateAssetProfileDialogParams
autoFocus: false, >(GfCreateAssetProfileDialogComponent, {
data: { autoFocus: false,
deviceType: this.deviceType, data: {
locale: this.user?.settings?.locale deviceType: this.deviceType,
} as CreateAssetProfileDialogParams, locale: this.user?.settings?.locale
width: this.deviceType === 'mobile' ? '100vw' : '50rem' },
} width: this.deviceType === 'mobile' ? '100vw' : '50rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()

62
apps/client/src/app/components/admin-platform/admin-platform.component.ts

@ -34,6 +34,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
import { GfCreateOrUpdatePlatformDialogComponent } from './create-or-update-platform-dialog/create-or-update-platform-dialog.component'; import { GfCreateOrUpdatePlatformDialogComponent } from './create-or-update-platform-dialog/create-or-update-platform-dialog.component';
import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -153,19 +154,20 @@ export class GfAdminPlatformComponent implements OnInit, OnDestroy {
} }
private openCreatePlatformDialog() { private openCreatePlatformDialog() {
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfCreateOrUpdatePlatformDialogComponent, GfCreateOrUpdatePlatformDialogComponent,
{ CreateOrUpdatePlatformDialogParams
data: { >(GfCreateOrUpdatePlatformDialogComponent, {
platform: { data: {
name: null, platform: {
url: null id: null,
} name: null,
}, url: null
height: this.deviceType === 'mobile' ? '98vh' : undefined, }
width: this.deviceType === 'mobile' ? '100vw' : '50rem' },
} height: this.deviceType === 'mobile' ? '98vh' : undefined,
); width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
dialogRef dialogRef
.afterClosed() .afterClosed()
@ -191,21 +193,29 @@ export class GfAdminPlatformComponent implements OnInit, OnDestroy {
}); });
} }
private openUpdatePlatformDialog({ id, name, url }) { private openUpdatePlatformDialog({
const dialogRef = this.dialog.open( id,
name,
url
}: {
id: string;
name: string;
url: string;
}) {
const dialogRef = this.dialog.open<
GfCreateOrUpdatePlatformDialogComponent, GfCreateOrUpdatePlatformDialogComponent,
{ CreateOrUpdatePlatformDialogParams
data: { >(GfCreateOrUpdatePlatformDialogComponent, {
platform: { data: {
id, platform: {
name, id,
url name,
} url
}, }
height: this.deviceType === 'mobile' ? '98vh' : undefined, },
width: this.deviceType === 'mobile' ? '100vw' : '50rem' height: this.deviceType === 'mobile' ? '98vh' : undefined,
} width: this.deviceType === 'mobile' ? '100vw' : '50rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()

14
apps/client/src/app/components/admin-tag/admin-tag.component.ts

@ -32,6 +32,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
import { GfCreateOrUpdateTagDialogComponent } from './create-or-update-tag-dialog/create-or-update-tag-dialog.component'; import { GfCreateOrUpdateTagDialogComponent } from './create-or-update-tag-dialog/create-or-update-tag-dialog.component';
import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -149,9 +150,13 @@ export class GfAdminTagComponent implements OnInit, OnDestroy {
} }
private openCreateTagDialog() { private openCreateTagDialog() {
const dialogRef = this.dialog.open(GfCreateOrUpdateTagDialogComponent, { const dialogRef = this.dialog.open<
GfCreateOrUpdateTagDialogComponent,
CreateOrUpdateTagDialogParams
>(GfCreateOrUpdateTagDialogComponent, {
data: { data: {
tag: { tag: {
id: null,
name: null name: null
} }
}, },
@ -183,8 +188,11 @@ export class GfAdminTagComponent implements OnInit, OnDestroy {
}); });
} }
private openUpdateTagDialog({ id, name }) { private openUpdateTagDialog({ id, name }: { id: string; name: string }) {
const dialogRef = this.dialog.open(GfCreateOrUpdateTagDialogComponent, { const dialogRef = this.dialog.open<
GfCreateOrUpdateTagDialogComponent,
CreateOrUpdateTagDialogParams
>(GfCreateOrUpdateTagDialogComponent, {
data: { data: {
tag: { tag: {
id, id,

2
apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/interfaces/interfaces.ts

@ -1,5 +1,5 @@
import { Tag } from '@prisma/client'; import { Tag } from '@prisma/client';
export interface CreateOrUpdateTagDialogParams { export interface CreateOrUpdateTagDialogParams {
tag: Tag; tag: Pick<Tag, 'id' | 'name'>;
} }

8
apps/client/src/app/components/admin-users/admin-users.component.ts

@ -288,14 +288,17 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
return; return;
} }
const dialogRef = this.dialog.open(GfUserDetailDialogComponent, { const dialogRef = this.dialog.open<
GfUserDetailDialogComponent,
UserDetailDialogParams
>(GfUserDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
userData, userData,
deviceType: this.deviceType, deviceType: this.deviceType,
hasPermissionForSubscription: this.hasPermissionForSubscription, hasPermissionForSubscription: this.hasPermissionForSubscription,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
} as UserDetailDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : '60vh', height: this.deviceType === 'mobile' ? '98vh' : '60vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -304,7 +307,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
this.fetchUsers();
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
}); });
} }

6
apps/client/src/app/components/header/header.component.ts

@ -1,4 +1,5 @@
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces';
import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component'; import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component';
import { LayoutService } from '@ghostfolio/client/core/layout.service'; import { LayoutService } from '@ghostfolio/client/core/layout.service';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
@ -271,7 +272,10 @@ export class GfHeaderComponent implements OnChanges {
} }
public openLoginDialog() { public openLoginDialog() {
const dialogRef = this.dialog.open(GfLoginWithAccessTokenDialogComponent, { const dialogRef = this.dialog.open<
GfLoginWithAccessTokenDialogComponent,
LoginWithAccessTokenDialogParams
>(GfLoginWithAccessTokenDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
accessToken: '', accessToken: '',

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

@ -149,17 +149,17 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit {
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfCreateWatchlistItemDialogComponent, GfCreateWatchlistItemDialogComponent,
{ CreateWatchlistItemDialogParams
autoFocus: false, >(GfCreateWatchlistItemDialogComponent, {
data: { autoFocus: false,
deviceType: this.deviceType, data: {
locale: this.user?.settings?.locale deviceType: this.deviceType,
} as CreateWatchlistItemDialogParams, locale: this.user?.settings?.locale
width: this.deviceType === 'mobile' ? '100vw' : '50rem' },
} width: this.deviceType === 'mobile' ? '100vw' : '50rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()

5
apps/client/src/app/components/login-with-access-token-dialog/interfaces/interfaces.ts

@ -0,0 +1,5 @@
export interface LoginWithAccessTokenDialogParams {
accessToken: string;
hasPermissionToUseSocialLogin: boolean;
title: string;
}

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

@ -26,6 +26,8 @@ import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { eyeOffOutline, eyeOutline } from 'ionicons/icons'; import { eyeOffOutline, eyeOutline } from 'ionicons/icons';
import { LoginWithAccessTokenDialogParams } from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
@ -51,7 +53,7 @@ export class GfLoginWithAccessTokenDialogComponent {
public isAccessTokenHidden = true; public isAccessTokenHidden = true;
public constructor( public constructor(
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: LoginWithAccessTokenDialogParams,
public dialogRef: MatDialogRef<GfLoginWithAccessTokenDialogComponent>, public dialogRef: MatDialogRef<GfLoginWithAccessTokenDialogComponent>,
private internetIdentityService: InternetIdentityService, private internetIdentityService: InternetIdentityService,
private router: Router, private router: Router,

7
apps/client/src/app/components/rule/rule.component.ts

@ -79,13 +79,16 @@ export class GfRuleComponent implements OnInit {
} }
public onCustomizeRule(rule: PortfolioReportRule) { public onCustomizeRule(rule: PortfolioReportRule) {
const dialogRef = this.dialog.open(GfRuleSettingsDialogComponent, { const dialogRef = this.dialog.open<
GfRuleSettingsDialogComponent,
RuleSettingsDialogParams
>(GfRuleSettingsDialogComponent, {
data: { data: {
rule, rule,
categoryName: this.categoryName, categoryName: this.categoryName,
locale: this.locale, locale: this.locale,
settings: this.settings settings: this.settings
} as RuleSettingsDialogParams, },
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

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

@ -31,6 +31,7 @@ import { EMPTY, Subject } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators'; import { catchError, takeUntil } from 'rxjs/operators';
import { GfCreateOrUpdateAccessDialogComponent } from './create-or-update-access-dialog/create-or-update-access-dialog.component'; import { GfCreateOrUpdateAccessDialogComponent } from './create-or-update-access-dialog/create-or-update-access-dialog.component';
import { CreateOrUpdateAccessDialogParams } from './create-or-update-access-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -187,10 +188,15 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit {
} }
private openCreateAccessDialog() { private openCreateAccessDialog() {
const dialogRef = this.dialog.open(GfCreateOrUpdateAccessDialogComponent, { const dialogRef = this.dialog.open<
GfCreateOrUpdateAccessDialogComponent,
CreateOrUpdateAccessDialogParams
>(GfCreateOrUpdateAccessDialogComponent, {
data: { data: {
access: { access: {
alias: '', alias: '',
grantee: null,
id: null,
permissions: ['READ_RESTRICTED'], permissions: ['READ_RESTRICTED'],
type: 'PRIVATE' type: 'PRIVATE'
} }
@ -219,12 +225,15 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit {
return; return;
} }
const dialogRef = this.dialog.open(GfCreateOrUpdateAccessDialogComponent, { const dialogRef = this.dialog.open<
GfCreateOrUpdateAccessDialogComponent,
CreateOrUpdateAccessDialogParams
>(GfCreateOrUpdateAccessDialogComponent, {
data: { data: {
access: { access: {
alias: access.alias, alias: access.alias,
id: access.id,
grantee: access.grantee === 'Public' ? null : access.grantee, grantee: access.grantee === 'Public' ? null : access.grantee,
id: access.id,
permissions: access.permissions, permissions: access.permissions,
type: access.type type: access.type
} }

24
apps/client/src/app/pages/about/overview/about-overview-page.html

@ -175,7 +175,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="mb-5 row">
<div <div
class="col-md-6 col-xs-12 my-2" class="col-md-6 col-xs-12 my-2"
[ngClass]="{ 'offset-md-3': hasPermissionForSubscription === false }" [ngClass]="{ 'offset-md-3': hasPermissionForSubscription === false }"
@ -201,4 +201,26 @@
</div> </div>
} }
</div> </div>
<div class="row">
<div class="col-12">
<h2 class="h4 mb-3">Sponsors</h2>
<div class="text-center">
<small>Browser testing via</small>
<br />
<a
href="https://www.lambdatest.com?utm_medium=sponsor&utm_source=ghostfolio"
target="_blank"
title="LambdaTest - AI Powered Testing Tool"
>
<img
alt="LambdaTest Logo"
height="45"
src="https://www.lambdatest.com/blue-logo.png"
width="250"
/>
</a>
</div>
</div>
</div>
</div> </div>

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

@ -23,6 +23,8 @@ import { EMPTY, Subject, Subscription } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators'; import { catchError, takeUntil } from 'rxjs/operators';
import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component'; import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component';
import { CreateOrUpdateAccountDialogParams } from './create-or-update-account-dialog/interfaces/interfaces';
import { TransferBalanceDialogParams } from './transfer-balance/interfaces/interfaces';
import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-balance-dialog.component'; import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-balance-dialog.component';
@Component({ @Component({
@ -179,7 +181,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
name, name,
platformId platformId
}: AccountModel) { }: AccountModel) {
const dialogRef = this.dialog.open(GfCreateOrUpdateAccountDialogComponent, { const dialogRef = this.dialog.open<
GfCreateOrUpdateAccountDialogComponent,
CreateOrUpdateAccountDialogParams
>(GfCreateOrUpdateAccountDialogComponent, {
data: { data: {
account: { account: {
balance, balance,
@ -227,7 +232,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
} }
private openAccountDetailDialog(aAccountId: string) { private openAccountDetailDialog(aAccountId: string) {
const dialogRef = this.dialog.open(GfAccountDetailDialogComponent, { const dialogRef = this.dialog.open<
GfAccountDetailDialogComponent,
AccountDetailDialogParams
>(GfAccountDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
accountId: aAccountId, accountId: aAccountId,
@ -237,7 +245,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
!this.hasImpersonationId && !this.hasImpersonationId &&
hasPermission(this.user?.permissions, permissions.createOrder) && hasPermission(this.user?.permissions, permissions.createOrder) &&
!this.user?.settings?.isRestrictedView !this.user?.settings?.isRestrictedView
} as AccountDetailDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -253,12 +261,16 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
} }
private openCreateAccountDialog() { private openCreateAccountDialog() {
const dialogRef = this.dialog.open(GfCreateOrUpdateAccountDialogComponent, { const dialogRef = this.dialog.open<
GfCreateOrUpdateAccountDialogComponent,
CreateOrUpdateAccountDialogParams
>(GfCreateOrUpdateAccountDialogComponent, {
data: { data: {
account: { account: {
balance: 0, balance: 0,
comment: null, comment: null,
currency: this.user?.settings?.baseCurrency, currency: this.user?.settings?.baseCurrency,
id: null,
isExcluded: false, isExcluded: false,
name: null, name: null,
platformId: null platformId: null
@ -295,7 +307,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit {
} }
private openTransferBalanceDialog() { private openTransferBalanceDialog() {
const dialogRef = this.dialog.open(GfTransferBalanceDialogComponent, { const dialogRef = this.dialog.open<
GfTransferBalanceDialogComponent,
TransferBalanceDialogParams
>(GfTransferBalanceDialogComponent, {
data: { data: {
accounts: this.accounts accounts: this.accounts
}, },

2
apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts

@ -1,5 +1,5 @@
import { Account } from '@prisma/client'; import { Account } from '@prisma/client';
export interface CreateOrUpdateAccountDialogParams { export interface CreateOrUpdateAccountDialogParams {
account: Account; account: Omit<Account, 'createdAt' | 'updatedAt' | 'userId'>;
} }

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

@ -28,6 +28,7 @@ import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { GfCreateOrUpdateActivityDialogComponent } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component'; import { GfCreateOrUpdateActivityDialogComponent } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component';
import { CreateOrUpdateActivityDialogParams } from './create-or-update-activity-dialog/interfaces/interfaces';
import { GfImportActivitiesDialogComponent } from './import-activities-dialog/import-activities-dialog.component'; import { GfImportActivitiesDialogComponent } from './import-activities-dialog/import-activities-dialog.component';
import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces';
@ -245,11 +246,14 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
} }
public onImport() { public onImport() {
const dialogRef = this.dialog.open(GfImportActivitiesDialogComponent, { const dialogRef = this.dialog.open<
GfImportActivitiesDialogComponent,
ImportActivitiesDialogParams
>(GfImportActivitiesDialogComponent, {
data: { data: {
deviceType: this.deviceType, deviceType: this.deviceType,
user: this.user user: this.user
} as ImportActivitiesDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : undefined, height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -268,12 +272,15 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
} }
public onImportDividends() { public onImportDividends() {
const dialogRef = this.dialog.open(GfImportActivitiesDialogComponent, { const dialogRef = this.dialog.open<
GfImportActivitiesDialogComponent,
ImportActivitiesDialogParams
>(GfImportActivitiesDialogComponent, {
data: { data: {
activityTypes: ['DIVIDEND'], activityTypes: ['DIVIDEND'],
deviceType: this.deviceType, deviceType: this.deviceType,
user: this.user user: this.user
} as ImportActivitiesDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : undefined, height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -306,18 +313,18 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
} }
public openUpdateActivityDialog(aActivity: Activity) { public openUpdateActivityDialog(aActivity: Activity) {
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfCreateOrUpdateActivityDialogComponent, GfCreateOrUpdateActivityDialogComponent,
{ CreateOrUpdateActivityDialogParams
data: { >(GfCreateOrUpdateActivityDialogComponent, {
activity: aActivity, data: {
accounts: this.user?.accounts, activity: aActivity,
user: this.user accounts: this.user?.accounts,
}, user: this.user
height: this.deviceType === 'mobile' ? '98vh' : '80vh', },
width: this.deviceType === 'mobile' ? '100vw' : '50rem' height: this.deviceType === 'mobile' ? '98vh' : '80vh',
} width: this.deviceType === 'mobile' ? '100vw' : '50rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()
@ -350,26 +357,26 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit {
.subscribe((user) => { .subscribe((user) => {
this.updateUser(user); this.updateUser(user);
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfCreateOrUpdateActivityDialogComponent, GfCreateOrUpdateActivityDialogComponent,
{ CreateOrUpdateActivityDialogParams
data: { >(GfCreateOrUpdateActivityDialogComponent, {
accounts: this.user?.accounts, data: {
activity: { accounts: this.user?.accounts,
...aActivity, activity: {
accountId: aActivity?.accountId, ...aActivity,
date: new Date(), accountId: aActivity?.accountId,
id: null, date: new Date(),
fee: 0, id: null,
type: aActivity?.type ?? 'BUY', fee: 0,
unitPrice: null type: aActivity?.type ?? 'BUY',
}, unitPrice: null
user: this.user
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', user: this.user
width: this.deviceType === 'mobile' ? '100vw' : '50rem' },
} height: this.deviceType === 'mobile' ? '98vh' : '80vh',
); width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
dialogRef dialogRef
.afterClosed() .afterClosed()

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

@ -4,7 +4,6 @@ import { User } from '@ghostfolio/common/interfaces';
import { Account } from '@prisma/client'; import { Account } from '@prisma/client';
export interface CreateOrUpdateActivityDialogParams { export interface CreateOrUpdateActivityDialogParams {
accountId: string;
accounts: Account[]; accounts: Account[];
activity: Activity; activity: Activity;
user: User; user: User;

2
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/interfaces/interfaces.ts

@ -3,7 +3,7 @@ import { User } from '@ghostfolio/common/interfaces';
import { Type } from '@prisma/client'; import { Type } from '@prisma/client';
export interface ImportActivitiesDialogParams { export interface ImportActivitiesDialogParams {
activityTypes: Type[]; activityTypes?: Type[];
deviceType: string; deviceType: string;
user: User; user: User;
} }

7
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -558,7 +558,10 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit {
} }
private openAccountDetailDialog(aAccountId: string) { private openAccountDetailDialog(aAccountId: string) {
const dialogRef = this.dialog.open(GfAccountDetailDialogComponent, { const dialogRef = this.dialog.open<
GfAccountDetailDialogComponent,
AccountDetailDialogParams
>(GfAccountDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: { data: {
accountId: aAccountId, accountId: aAccountId,
@ -568,7 +571,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit {
!this.hasImpersonationId && !this.hasImpersonationId &&
hasPermission(this.user?.permissions, permissions.createOrder) && hasPermission(this.user?.permissions, permissions.createOrder) &&
!this.user?.settings?.isRestrictedView !this.user?.settings?.isRestrictedView
} as AccountDetailDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

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

@ -84,18 +84,18 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit {
} }
public openShowAccessTokenDialog() { public openShowAccessTokenDialog() {
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfUserAccountRegistrationDialogComponent, GfUserAccountRegistrationDialogComponent,
{ UserAccountRegistrationDialogParams
data: { >(GfUserAccountRegistrationDialogComponent, {
deviceType: this.deviceType, data: {
needsToAcceptTermsOfService: this.hasPermissionForSubscription deviceType: this.deviceType,
} as UserAccountRegistrationDialogParams, needsToAcceptTermsOfService: this.hasPermissionForSubscription
disableClose: true, },
height: this.deviceType === 'mobile' ? '98vh' : undefined, disableClose: true,
width: this.deviceType === 'mobile' ? '100vw' : '30rem' height: this.deviceType === 'mobile' ? '98vh' : undefined,
} width: this.deviceType === 'mobile' ? '100vw' : '30rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()

18
apps/client/src/app/pages/resources/glossary/resources-glossary.component.html

@ -5,7 +5,7 @@
<div> <div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Buy and Hold</h3> <h2 class="h5 mb-1 mt-0">Buy and Hold</h2>
<div class="mb-1"> <div class="mb-1">
Buy and hold is a passive investment strategy where you buy assets Buy and hold is a passive investment strategy where you buy assets
and hold them for a long period regardless of fluctuations in the and hold them for a long period regardless of fluctuations in the
@ -22,7 +22,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Deflation</h3> <h2 class="h5 mb-1 mt-0">Deflation</h2>
<div class="mb-1"> <div class="mb-1">
Deflation is a decrease of the general price level for goods and Deflation is a decrease of the general price level for goods and
services in an economy over a period of time. services in an economy over a period of time.
@ -38,7 +38,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Dollar-Cost Averaging (DCA)</h3> <h2 class="h5 mb-1 mt-0">Dollar-Cost Averaging (DCA)</h2>
<div class="mb-1"> <div class="mb-1">
Dollar-cost averaging is an investment strategy where you split Dollar-cost averaging is an investment strategy where you split
the total amount to be invested across periodic purchases of a the total amount to be invested across periodic purchases of a
@ -56,7 +56,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Financial Independence</h3> <h2 class="h5 mb-1 mt-0">Financial Independence</h2>
<div class="mb-1"> <div class="mb-1">
Financial independence is the status of having enough income, for Financial independence is the status of having enough income, for
example with a passive income like dividends, to cover your living example with a passive income like dividends, to cover your living
@ -73,7 +73,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">FIRE</h3> <h2 class="h5 mb-1 mt-0">FIRE</h2>
<div class="mb-1"> <div class="mb-1">
FIRE is a movement that promotes saving and investing to achieve FIRE is a movement that promotes saving and investing to achieve
financial independence and early retirement. financial independence and early retirement.
@ -85,7 +85,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Inflation</h3> <h2 class="h5 mb-1 mt-0">Inflation</h2>
<div class="mb-1"> <div class="mb-1">
Inflation is an increase of the general price level for goods and Inflation is an increase of the general price level for goods and
services in an economy over a period of time. services in an economy over a period of time.
@ -102,7 +102,7 @@
@if (hasPermissionForSubscription) { @if (hasPermissionForSubscription) {
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Personal Finance Tools</h3> <h2 class="h5 mb-1 mt-0">Personal Finance Tools</h2>
<div class="mb-1"> <div class="mb-1">
Personal finance tools are software applications that help Personal finance tools are software applications that help
manage your money, track expenses, set budgets, monitor manage your money, track expenses, set budgets, monitor
@ -118,7 +118,7 @@
} }
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Stagflation</h3> <h2 class="h5 mb-1 mt-0">Stagflation</h2>
<div class="mb-1"> <div class="mb-1">
Stagflation describes a situation in which there is a stagnant Stagflation describes a situation in which there is a stagnant
economy with high unemployment and high inflation. economy with high unemployment and high inflation.
@ -134,7 +134,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Stealth Wealth</h3> <h2 class="h5 mb-1 mt-0">Stealth Wealth</h2>
<div class="mb-1"> <div class="mb-1">
Stealth wealth is a lifestyle choice where you don’t openly show Stealth wealth is a lifestyle choice where you don’t openly show
off your wealth, but instead live quietly to maintain privacy and off your wealth, but instead live quietly to maintain privacy and

4
apps/client/src/app/pages/resources/guides/resources-guides.component.html

@ -5,7 +5,7 @@
<div class="mb-5"> <div class="mb-5">
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Boringly Getting Rich</h3> <h2 class="h5 mb-1 mt-0">Boringly Getting Rich</h2>
<div class="mb-1"> <div class="mb-1">
The <i>Boringly Getting Rich</i> guide supports you to get started The <i>Boringly Getting Rich</i> guide supports you to get started
with investing. It introduces a strategy utilizing a broadly with investing. It introduces a strategy utilizing a broadly
@ -21,7 +21,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">How do I get my finances in order?</h3> <h2 class="h5 mb-1 mt-0">How do I get my finances in order?</h2>
<div class="mb-1"> <div class="mb-1">
Before you can think of long-term investing, you have to get your Before you can think of long-term investing, you have to get your
finances in order. Learn how you can reach your financial goals finances in order. Learn how you can reach your financial goals

8
apps/client/src/app/pages/resources/markets/resources-markets.component.html

@ -3,7 +3,7 @@
<div class="mb-5"> <div class="mb-5">
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Crypto Coins Heatmap</h3> <h2 class="h5 mb-1 mt-0">Crypto Coins Heatmap</h2>
<div class="mb-1"> <div class="mb-1">
With the <i>Crypto Coins Heatmap</i> you can track the daily market With the <i>Crypto Coins Heatmap</i> you can track the daily market
movements of cryptocurrencies as a visual snapshot. movements of cryptocurrencies as a visual snapshot.
@ -17,7 +17,7 @@
</div> </div>
<div class="mb-4 media"> <div class="mb-4 media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Fear & Greed Index</h3> <h2 class="h5 mb-1 mt-0">Fear & Greed Index</h2>
<div class="mb-1"> <div class="mb-1">
The fear and greed index was developed by <i>CNNMoney</i> to measure The fear and greed index was developed by <i>CNNMoney</i> to measure
the primary emotions (fear and greed) that influence how much the primary emotions (fear and greed) that influence how much
@ -32,7 +32,7 @@
</div> </div>
<div class="media"> <div class="media">
<div class="mb-4 media"> <div class="mb-4 media">
<h3 class="h5 mt-0">Inflation Chart</h3> <h2 class="h5 mb-1 mt-0">Inflation Chart</h2>
<div class="mb-1"> <div class="mb-1">
<i>Inflation Chart</i> helps you find the intrinsic value of stock <i>Inflation Chart</i> helps you find the intrinsic value of stock
markets, stock prices, goods and services by adjusting them to the markets, stock prices, goods and services by adjusting them to the
@ -48,7 +48,7 @@
</div> </div>
<div class="media"> <div class="media">
<div class="media-body"> <div class="media-body">
<h3 class="h5 mt-0">Stock Heatmap</h3> <h2 class="h5 mb-1 mt-0">Stock Heatmap</h2>
<div class="mb-1"> <div class="mb-1">
With the <i>Stock Heatmap</i> you can track the daily market movements With the <i>Stock Heatmap</i> you can track the daily market movements
of stocks as a visual snapshot. of stocks as a visual snapshot.

2
apps/client/src/app/pages/resources/overview/resources-overview.component.html

@ -5,7 +5,7 @@
<div class="overview-list"> <div class="overview-list">
@for (item of overviewItems; track item) { @for (item of overviewItems; track item) {
<div class="mb-4"> <div class="mb-4">
<h3 class="h5 mt-0">{{ item.title }}</h3> <h2 class="h5 mb-1 mt-0">{{ item.title }}</h2>
<p class="mb-1">{{ item.description }}</p> <p class="mb-1">{{ item.description }}</p>
<a [routerLink]="item.routerLink">Explore {{ item.title }} →</a> <a [routerLink]="item.routerLink">Explore {{ item.title }} →</a>
</div> </div>

22
apps/client/src/app/services/user/user.service.ts

@ -116,18 +116,18 @@ export class UserService extends ObservableStore<UserStoreState> {
permissions.enableSubscriptionInterstitial permissions.enableSubscriptionInterstitial
) )
) { ) {
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfSubscriptionInterstitialDialogComponent, GfSubscriptionInterstitialDialogComponent,
{ SubscriptionInterstitialDialogParams
autoFocus: false, >(GfSubscriptionInterstitialDialogComponent, {
data: { autoFocus: false,
user data: {
} as SubscriptionInterstitialDialogParams, user
disableClose: true, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', disableClose: true,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' height: this.deviceType === 'mobile' ? '98vh' : '80vh',
} width: this.deviceType === 'mobile' ? '100vw' : '50rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()

7
libs/ui/src/lib/benchmark/benchmark.component.ts

@ -155,14 +155,17 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
dataSource, dataSource,
symbol symbol
}: AssetProfileIdentifier) { }: AssetProfileIdentifier) {
const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, { const dialogRef = this.dialog.open<
GfBenchmarkDetailDialogComponent,
BenchmarkDetailDialogParams
>(GfBenchmarkDetailDialogComponent, {
data: { data: {
dataSource, dataSource,
symbol, symbol,
colorScheme: this.user?.settings?.colorScheme, colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
locale: this.locale locale: this.locale
} as BenchmarkDetailDialogParams, },
height: this.deviceType === 'mobile' ? '98vh' : undefined, height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

28
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts

@ -199,21 +199,21 @@ export class GfHistoricalMarketDataEditorComponent
}) { }) {
const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice; const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice;
const dialogRef = this.dialog.open( const dialogRef = this.dialog.open<
GfHistoricalMarketDataEditorDialogComponent, GfHistoricalMarketDataEditorDialogComponent,
{ HistoricalMarketDataEditorDialogParams
data: { >(GfHistoricalMarketDataEditorDialogComponent, {
marketPrice, data: {
currency: this.currency, marketPrice,
dataSource: this.dataSource, currency: this.currency,
dateString: `${yearMonth}-${day}`, dataSource: this.dataSource,
symbol: this.symbol, dateString: `${yearMonth}-${day}`,
user: this.user symbol: this.symbol,
} as HistoricalMarketDataEditorDialogParams, user: this.user
height: this.deviceType === 'mobile' ? '98vh' : '80vh', },
width: this.deviceType === 'mobile' ? '100vw' : '50rem' height: this.deviceType === 'mobile' ? '98vh' : '80vh',
} width: this.deviceType === 'mobile' ? '100vw' : '50rem'
); });
dialogRef dialogRef
.afterClosed() .afterClosed()

4
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.211.0", "version": "2.212.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.211.0", "version": "2.212.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": "2.211.0", "version": "2.212.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