From 9439b9ef1f6e39eafeb77c25b8476a1b1da81c98 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:51:12 +0700 Subject: [PATCH] Task/enable strictTemplates for the client app (#8108) Enable strictTemplates --- .../account-detail-dialog.component.ts | 2 -- .../account-detail-dialog.html | 1 - .../holding-detail-dialog.component.ts | 2 -- .../holding-detail-dialog.html | 1 - .../components/markets/markets.component.ts | 24 +++++++++----- .../src/app/components/markets/markets.html | 2 +- .../interfaces/interfaces.ts | 2 +- .../src/app/components/rule/rule.component.ts | 26 +++++++-------- .../app/components/rules/rules.component.ts | 11 +++---- .../import-activities-dialog.html | 2 -- .../app/pages/portfolio/fire/fire-page.html | 2 +- .../src/app/pages/pricing/pricing-page.html | 32 ++++++++++--------- apps/client/tsconfig.json | 20 +++++------- 13 files changed, 61 insertions(+), 66 deletions(-) diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index c95dfe7bc4..9a1dfe9438 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -35,7 +35,6 @@ import { inject, OnInit } from '@angular/core'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @@ -82,7 +81,6 @@ import { MatTabsModule, NgxSkeletonLoaderModule ], - schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-account-detail-dialog', styleUrls: ['./account-detail-dialog.component.scss'], templateUrl: 'account-detail-dialog.html' diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html index 5a86e4aa9b..7f50255012 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html @@ -153,7 +153,6 @@ [hasPermissionToCreateActivity]="data.hasPermissionToCreateActivity" [hasPermissionToDeleteActivity]="false" [hasPermissionToExportActivities]="!showValuesInPercentage()" - [hasPermissionToFilter]="false" [hasPermissionToOpenDetails]="false" [hasPermissionToUpdateActivity]="data.hasPermissionToUpdateActivity" [locale]="user?.settings?.locale" diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 8c3ec2206e..e08e4bfed6 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -41,7 +41,6 @@ import { GfTagsSelectorComponent } from '@ghostfolio/ui/tags-selector'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { - CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -116,7 +115,6 @@ import { ReactiveFormsModule, RouterModule ], - schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-holding-detail-dialog', styleUrls: ['./holding-detail-dialog.component.scss'], templateUrl: 'holding-detail-dialog.html' diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index b54cc68f7d..494c61a021 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -383,7 +383,6 @@ [hasPermissionToExportActivities]=" !data.impersonationId && !user?.settings?.isRestrictedView " - [hasPermissionToFilter]="false" [hasPermissionToOpenDetails]="false" [hasPermissionToUpdateActivity]="data.hasPermissionToUpdateActivity" [locale]="data.locale" diff --git a/apps/client/src/app/components/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index 3c8fa50f95..1c8fdb1e1d 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -3,8 +3,8 @@ import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { resetHours } from '@ghostfolio/common/helper'; import { Benchmark, - HistoricalDataItem, InfoItem, + LineChartItem, MarketDataOfMarketsResponse, User } from '@ghostfolio/common/interfaces'; @@ -28,6 +28,7 @@ import { signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { isNumber } from 'lodash-es'; import { DeviceDetectorService } from 'ngx-device-detector'; @Component({ @@ -66,7 +67,7 @@ export class GfMarketsComponent implements OnInit { protected fearAndGreedIndexMode: FearAndGreedIndexMode = 'STOCKS'; protected hasPermissionToAccessFearAndGreedIndex: boolean; protected hasPermissionToReadMarketDataOfMarkets: boolean; - protected historicalDataItems: HistoricalDataItem[]; + protected historicalDataItems: LineChartItem[]; protected isLoadingFearAndGreedIndex = true; protected user: User; @@ -158,13 +159,20 @@ export class GfMarketsComponent implements OnInit { this.fearAndGreedIndex = this.fearAndGreedIndexData[this.fearAndGreedIndexMode]?.marketPrice; - this.historicalDataItems = [ - ...(this.fearAndGreedIndexData[this.fearAndGreedIndexMode] - ?.historicalData ?? []), - { + const historicalDataItems = ( + this.fearAndGreedIndexData[this.fearAndGreedIndexMode]?.historicalData ?? + [] + ).filter((item): item is LineChartItem => { + return isNumber(item.value); + }); + + if (isNumber(this.fearAndGreedIndex)) { + historicalDataItems.push({ date: resetHours(new Date()).toISOString(), value: this.fearAndGreedIndex - } - ]; + }); + } + + this.historicalDataItems = historicalDataItems; } } diff --git a/apps/client/src/app/components/markets/markets.html b/apps/client/src/app/components/markets/markets.html index 68b22d26f0..9131282ea0 100644 --- a/apps/client/src/app/components/markets/markets.html +++ b/apps/client/src/app/components/markets/markets.html @@ -29,7 +29,7 @@ [colorScheme]="user?.settings?.colorScheme" [historicalDataItems]="historicalDataItems" [isAnimated]="true" - [locale]="user?.settings?.locale || undefined" + [locale]="user?.settings?.locale ?? DEFAULT_LOCALE" [showXAxis]="true" [showYAxis]="true" [yMax]="100" diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts index 90a0039cf0..0c66f5d4e4 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts @@ -5,7 +5,7 @@ import { export interface RuleSettingsDialogParams { categoryName: string; - locale: string; + locale?: string; rule: PortfolioReportRule; settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; } diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index 7edc713cde..da6bf39ff4 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -8,11 +8,10 @@ import { import { ChangeDetectionStrategy, Component, + computed, DestroyRef, - EventEmitter, Input, - OnInit, - Output + output } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; @@ -41,17 +40,20 @@ import { GfRuleSettingsDialogComponent } from './rule-settings-dialog/rule-setti styleUrls: ['./rule.component.scss'], templateUrl: './rule.component.html' }) -export class GfRuleComponent implements OnInit { +export class GfRuleComponent { @Input() categoryName: string; @Input() hasPermissionToUpdateUserSettings: boolean; @Input() isLoading: boolean; - @Input() locale: string; + @Input() locale?: string; @Input() rule: PortfolioReportRule; @Input() settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; - @Output() ruleUpdated = new EventEmitter(); + public readonly ruleUpdated = output(); + + private readonly deviceType = computed(() => { + return this.deviceDetectorService.deviceInfo().deviceType; + }); - private deviceType: string; public constructor( private destroyRef: DestroyRef, private deviceDetectorService: DeviceDetectorService, @@ -67,11 +69,7 @@ export class GfRuleComponent implements OnInit { }); } - public ngOnInit() { - this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; - } - - public onCustomizeRule(rule: PortfolioReportRule) { + protected onCustomizeRule(rule: PortfolioReportRule) { const dialogRef = this.dialog.open< GfRuleSettingsDialogComponent, RuleSettingsDialogParams @@ -82,7 +80,7 @@ export class GfRuleComponent implements OnInit { locale: this.locale, settings: this.settings }, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef @@ -99,7 +97,7 @@ export class GfRuleComponent implements OnInit { }); } - public onUpdateRule(rule: PortfolioReportRule) { + protected onUpdateRule(rule: PortfolioReportRule) { const settings: UpdateUserSettingDto = { xRayRules: { [rule.key]: { isActive: !rule.isActive } diff --git a/apps/client/src/app/components/rules/rules.component.ts b/apps/client/src/app/components/rules/rules.component.ts index 22e1718f8c..97c21589e0 100644 --- a/apps/client/src/app/components/rules/rules.component.ts +++ b/apps/client/src/app/components/rules/rules.component.ts @@ -8,9 +8,8 @@ import { import { ChangeDetectionStrategy, Component, - EventEmitter, Input, - Output + output } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; @@ -26,13 +25,13 @@ export class GfRulesComponent { @Input() categoryName: string; @Input() hasPermissionToUpdateUserSettings: boolean; @Input() isLoading: boolean; - @Input() locale: string; + @Input() locale?: string; @Input() rules: PortfolioReportRule[]; - @Input() settings: XRayRulesSettings; + @Input() settings?: XRayRulesSettings; - @Output() rulesUpdated = new EventEmitter(); + public readonly rulesUpdated = output(); - public onRuleUpdated(event: UpdateUserSettingDto) { + protected onRuleUpdated(event: UpdateUserSettingDto) { this.rulesUpdated.emit(event); } } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html index 559f639b15..911be41056 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -128,14 +128,12 @@ [hasPermissionToCreateActivity]="false" [hasPermissionToDeleteActivity]="false" [hasPermissionToExportActivities]="false" - [hasPermissionToFilter]="false" [hasPermissionToOpenDetails]="false" [locale]="data?.user?.settings?.locale" [pageIndex]="pageIndex" [pageSize]="pageSize" [showActions]="false" [showCheckbox]="true" - [showSymbolColumn]="false" [sortColumn]="sortColumn" [sortDirection]="sortDirection" [sortDisabled]="true" diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index 3f12ddc126..004bc1ed25 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -178,7 +178,7 @@ [isPercent]="true" [locale]="user?.settings?.locale" [precision]="2" - [value]="user?.settings?.annualInterestRate / 100" + [value]="(user?.settings?.annualInterestRate ?? 0) / 100" /> annual interest rate. diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index cf3bd2448a..8ab170b4b5 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -258,21 +258,23 @@

Fully managed Ghostfolio cloud offering.

-

- - @if (coupon) { - {{ baseCurrency }} {{ price }} {{ baseCurrency }} {{ - price - coupon - }} - } @else { - {{ baseCurrency }} {{ price }} - } -   - per year -

+ @if (price) { +

+ + @if (coupon) { + {{ baseCurrency }} {{ price }} {{ baseCurrency }} {{ + price - coupon + }} + } @else { + {{ baseCurrency }} {{ price }} + } +   + per year +

+ } @if ( hasPermissionToUpdateUserSettings && user?.subscription?.type === 'Basic' diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json index a6815ec125..b009454a0d 100644 --- a/apps/client/tsconfig.json +++ b/apps/client/tsconfig.json @@ -14,20 +14,16 @@ } ], "angularCompilerOptions": { + "extendedDiagnostics": { + "checks": { + // TODO: Remove once the fields are typed as optional + "nullishCoalescingNotNullable": "suppress", + "optionalChainNotNullable": "suppress" + } + }, "strictInjectionParameters": true, - // TODO: Remove these subsets once strictTemplates is enabled - "strictAttributeTypes": true, - "strictContextGenerics": true, - "strictDomEventTypes": true, - "strictDomLocalRefTypes": true, "strictInputAccessModifiers": true, - "strictInputTypes": true, - "strictLiteralTypes": true, - "strictNullInputTypes": true, - "strictOutputEventTypes": true, - "strictSafeNavigationTypes": true, - // TODO: Enable stricter rules for this project - "strictTemplates": false + "strictTemplates": true }, "compilerOptions": { "module": "preserve",