Browse Source

Merge branch 'main' of https://github.com/yksolanki9/ghostfolio into feature/add-accounts-import-export

pull/1635/head
yksolanki9 3 years ago
parent
commit
f995fabbf7
  1. 27
      CHANGELOG.md
  2. 2
      README.md
  3. 3
      apps/api/src/app/account/account.controller.ts
  4. 20
      apps/api/src/app/app.module.ts
  5. 1
      apps/api/src/app/import/import.controller.ts
  6. 27
      apps/api/src/app/order/order.service.ts
  7. 2
      apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts
  8. 1
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts
  9. 1
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts
  10. 1
      apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  11. 1
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  12. 1
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts
  13. 1
      apps/api/src/app/portfolio/portfolio-calculator.ts
  14. 7
      apps/api/src/app/portfolio/portfolio.controller.ts
  15. 57
      apps/api/src/app/portfolio/portfolio.service.ts
  16. 3
      apps/api/src/app/user/user.controller.ts
  17. 26
      apps/api/src/app/user/user.service.ts
  18. 1
      apps/api/src/interceptors/redact-values-in-response.interceptor.ts
  19. 2
      apps/api/src/services/configuration.service.ts
  20. 18
      apps/client/.browserslistrc
  21. 5
      apps/client/project.json
  22. 3
      apps/client/src/app/app-routing.module.ts
  23. 31
      apps/client/src/app/app.component.ts
  24. 16
      apps/client/src/app/app.module.ts
  25. 2
      apps/client/src/app/components/access-table/access-table.component.ts
  26. 6
      apps/client/src/app/components/access-table/access-table.module.ts
  27. 5
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  28. 4
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.module.ts
  29. 2
      apps/client/src/app/components/accounts-table/accounts-table.component.ts
  30. 8
      apps/client/src/app/components/accounts-table/accounts-table.module.ts
  31. 6
      apps/client/src/app/components/admin-jobs/admin-jobs.module.ts
  32. 2
      apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts
  33. 7
      apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts
  34. 8
      apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts
  35. 4
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  36. 8
      apps/client/src/app/components/admin-market-data/admin-market-data.module.ts
  37. 5
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  38. 8
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts
  39. 2
      apps/client/src/app/components/admin-overview/admin-overview.component.ts
  40. 8
      apps/client/src/app/components/admin-overview/admin-overview.module.ts
  41. 4
      apps/client/src/app/components/admin-users/admin-users.module.ts
  42. 2
      apps/client/src/app/components/benchmark-comparator/benchmark-comparator.module.ts
  43. 2
      apps/client/src/app/components/dialog-footer/dialog-footer.module.ts
  44. 2
      apps/client/src/app/components/dialog-header/dialog-header.module.ts
  45. 2
      apps/client/src/app/components/header/header.component.ts
  46. 4
      apps/client/src/app/components/header/header.module.ts
  47. 2
      apps/client/src/app/components/home-holdings/home-holdings.component.ts
  48. 4
      apps/client/src/app/components/home-holdings/home-holdings.module.ts
  49. 8
      apps/client/src/app/components/home-summary/home-summary.component.ts
  50. 2
      apps/client/src/app/components/home-summary/home-summary.module.ts
  51. 7
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.component.ts
  52. 10
      apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts
  53. 15
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts
  54. 20
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
  55. 6
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts
  56. 2
      apps/client/src/app/components/position/position.module.ts
  57. 2
      apps/client/src/app/components/positions/positions.module.ts
  58. 4
      apps/client/src/app/components/rules/rules.module.ts
  59. 1
      apps/client/src/app/components/subscription-interstitial-dialog/interfaces/interfaces.ts
  60. 25
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts
  61. 42
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html
  62. 21
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.module.ts
  63. 11
      apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.scss
  64. 2
      apps/client/src/app/components/toggle/toggle.module.ts
  65. 8
      apps/client/src/app/core/http-response.interceptor.ts
  66. 4
      apps/client/src/app/pages/about/about-page.module.ts
  67. 2
      apps/client/src/app/pages/about/changelog/changelog-page.module.ts
  68. 16
      apps/client/src/app/pages/account/account-page.component.ts
  69. 14
      apps/client/src/app/pages/account/account-page.module.ts
  70. 7
      apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  71. 10
      apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.module.ts
  72. 2
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  73. 2
      apps/client/src/app/pages/accounts/accounts-page.module.ts
  74. 7
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
  75. 12
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.module.ts
  76. 8
      apps/client/src/app/pages/admin/admin-page.module.ts
  77. 2
      apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.module.ts
  78. 2
      apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.module.ts
  79. 2
      apps/client/src/app/pages/blog/blog-page.module.ts
  80. 2
      apps/client/src/app/pages/faq/faq-page.module.ts
  81. 4
      apps/client/src/app/pages/features/features-page.module.ts
  82. 2
      apps/client/src/app/pages/home/home-page.module.ts
  83. 8
      apps/client/src/app/pages/landing/landing-page.html
  84. 4
      apps/client/src/app/pages/landing/landing-page.module.ts
  85. 5
      apps/client/src/app/pages/landing/landing-page.scss
  86. 2
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  87. 4
      apps/client/src/app/pages/portfolio/activities/activities-page.module.ts
  88. 7
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  89. 8
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  90. 16
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.module.ts
  91. 9
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  92. 8
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts
  93. 9
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  94. 6
      apps/client/src/app/pages/portfolio/allocations/allocations-page.module.ts
  95. 9
      apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts
  96. 2
      apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts
  97. 2
      apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts
  98. 2
      apps/client/src/app/pages/portfolio/holdings/holdings-page.module.ts
  99. 2
      apps/client/src/app/pages/portfolio/portfolio-page.module.ts
  100. 171
      apps/client/src/app/pages/pricing/pricing-page.html

27
CHANGELOG.md

@ -5,15 +5,40 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 1.231.0 - 2023-02-04
### Added
- Added the dividend and fees to the position detail dialog
- Added support to link a (wealth) item to an account
### Changed
- Relaxed the validation rule of the _Redis_ host environment variable (`REDIS_HOST`)
- Improved the language localization for German (`de`)
- Eliminated `angular-material-css-vars`
- Upgraded `angular` from version `14.2.0` to `15.1.2`
- Upgraded `Nx` from version `15.0.13` to `15.6.3`
## 1.230.0 - 2023-01-29
### Added
- Added an interstitial for the subscription
- Added _SourceForge_ to the _As seen in_ section on the landing page
- Added a quote to the blog post _Ghostfolio auf Sackgeld.com vorgestellt_
### Changed
- Improved the unit format (`%`) in the global heat map component of the public page
- Improved the pricing page
- Upgraded `Node.js` from version `16` to `18` (`Dockerfile`)
- Upgraded `prisma` from version `4.8.0` to `4.9.0`
### Fixed
- Fixed the click of unknown accounts in the portfolio proportion chart component
- Fixed an issue with `value` in the value redaction interceptor for the impersonation mode
## 1.229.0 - 2023-01-21

2
README.md

@ -40,7 +40,7 @@ Ghostfolio is for you if you are...
- 🧘 into minimalism
- 🧺 caring about diversifying your financial resources
- 🆓 interested in financial independence
- 🙅 saying no to spreadsheets in 2023
- 🙅 saying no to spreadsheets
- 😎 still reading this list
## Features

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

@ -37,8 +37,7 @@ export class AccountController {
private readonly accountService: AccountService,
private readonly impersonationService: ImpersonationService,
private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly userService: UserService
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Delete(':id')

20
apps/api/src/app/app.module.ts

@ -1,24 +1,23 @@
import { join } from 'path';
import { AuthDeviceModule } from '@ghostfolio/api/app/auth-device/auth-device.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
import { CronService } from '@ghostfolio/api/services/cron.service';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module';
import { BullModule } from '@nestjs/bull';
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import { ServeStaticModule } from '@nestjs/serve-static';
import { ConfigurationModule } from '../services/configuration.module';
import { CronService } from '../services/cron.service';
import { DataGatheringModule } from '../services/data-gathering.module';
import { DataProviderModule } from '../services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '../services/exchange-rate-data.module';
import { PrismaModule } from '../services/prisma.module';
import { TwitterBotModule } from '../services/twitter-bot/twitter-bot.module';
import { AccessModule } from './access/access.module';
import { AccountModule } from './account/account.module';
import { AdminModule } from './admin/admin.module';
import { AppController } from './app.controller';
import { AuthDeviceModule } from './auth-device/auth-device.module';
import { AuthModule } from './auth/auth.module';
import { BenchmarkModule } from './benchmark/benchmark.module';
import { CacheModule } from './cache/cache.module';
@ -30,6 +29,7 @@ import { InfoModule } from './info/info.module';
import { LogoModule } from './logo/logo.module';
import { OrderModule } from './order/order.module';
import { PortfolioModule } from './portfolio/portfolio.module';
import { RedisCacheModule } from './redis-cache/redis-cache.module';
import { SubscriptionModule } from './subscription/subscription.module';
import { SymbolModule } from './symbol/symbol.module';
import { UserModule } from './user/user.module';
@ -45,7 +45,7 @@ import { UserModule } from './user/user.module';
BullModule.forRoot({
redis: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT, 10),
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
password: process.env.REDIS_PASSWORD
}
}),

1
apps/api/src/app/import/import.controller.ts

@ -21,7 +21,6 @@ import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { isEmpty } from 'lodash';
import { ImportDataDto } from './import-data.dto';
import { ImportService } from './import.service';

27
apps/api/src/app/order/order.service.ts

@ -76,23 +76,21 @@ export class OrderService {
userId: string;
}
): Promise<Order> {
const defaultAccount = (
await this.accountService.getAccounts(data.userId)
).find((account) => {
return account.isDefault === true;
});
let Account;
if (data.accountId) {
Account = {
connect: {
id_userId: {
userId: data.userId,
id: data.accountId
}
}
};
}
const tags = data.tags ?? [];
let Account = {
connect: {
id_userId: {
userId: data.userId,
id: data.accountId ?? defaultAccount?.id
}
}
};
if (data.type === 'ITEM') {
const assetClass = data.assetClass;
const assetSubClass = data.assetSubClass;
@ -101,7 +99,6 @@ export class OrderService {
const id = uuidv4();
const name = data.SymbolProfile.connectOrCreate.create.symbol;
Account = undefined;
data.id = id;
data.SymbolProfile.connectOrCreate.create.assetClass = assetClass;
data.SymbolProfile.connectOrCreate.create.assetSubClass = assetSubClass;

2
apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts

@ -7,6 +7,8 @@ import { Tag } from '@prisma/client';
export interface PortfolioPositionDetail {
averagePrice: number;
dividendInBaseCurrency: number;
feeInBaseCurrency: number;
firstBuyDate: string;
grossPerformance: number;
grossPerformancePercent: number;

1
apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -82,6 +82,7 @@ describe('PortfolioCalculator', () => {
averagePrice: new Big('0'),
currency: 'CHF',
dataSource: 'YAHOO',
fee: new Big('3.2'),
firstBuyDate: '2021-11-22',
grossPerformance: new Big('-12.6'),
grossPerformancePercentage: new Big('-0.0440867739678096571'),

1
apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts

@ -71,6 +71,7 @@ describe('PortfolioCalculator', () => {
averagePrice: new Big('136.6'),
currency: 'CHF',
dataSource: 'YAHOO',
fee: new Big('1.55'),
firstBuyDate: '2021-11-30',
grossPerformance: new Big('24.6'),
grossPerformancePercentage: new Big('0.09004392386530014641'),

1
apps/api/src/app/portfolio/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -82,6 +82,7 @@ describe('PortfolioCalculator', () => {
averagePrice: new Big('320.43'),
currency: 'CHF',
dataSource: 'YAHOO',
fee: new Big('0'),
firstBuyDate: '2015-01-01',
grossPerformance: new Big('27172.74'),
grossPerformancePercentage: new Big('42.40043067128546016291'),

1
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -82,6 +82,7 @@ describe('PortfolioCalculator', () => {
averagePrice: new Big('75.80'),
currency: 'CHF',
dataSource: 'YAHOO',
fee: new Big('4.25'),
firstBuyDate: '2022-03-07',
grossPerformance: new Big('21.93'),
grossPerformancePercentage: new Big('0.14465699208443271768'),

1
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -102,6 +102,7 @@ describe('PortfolioCalculator', () => {
averagePrice: new Big('0'),
currency: 'CHF',
dataSource: 'YAHOO',
fee: new Big('0'),
firstBuyDate: '2022-03-07',
grossPerformance: new Big('19.86'),
grossPerformancePercentage: new Big('0.13100263852242744063'),

1
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -431,6 +431,7 @@ export class PortfolioCalculator {
: item.investment.div(item.quantity),
currency: item.currency,
dataSource: item.dataSource,
fee: item.fee,
firstBuyDate: item.firstBuyDate,
grossPerformance: !hasErrors ? grossPerformance ?? null : null,
grossPerformancePercentage: !hasErrors

7
apps/api/src/app/portfolio/portfolio.controller.ts

@ -131,7 +131,8 @@ export class PortfolioController {
portfolioPosition.investment / totalInvestment;
portfolioPosition.netPerformance = null;
portfolioPosition.quantity = null;
portfolioPosition.value = portfolioPosition.value / totalValue;
portfolioPosition.valueInPercentage =
portfolioPosition.value / totalValue;
}
for (const [name, { current, original }] of Object.entries(accounts)) {
@ -322,7 +323,7 @@ export class PortfolioController {
totalInvestment: new Big(totalInvestment)
.div(performanceInformation.performance.totalInvestment)
.toNumber(),
value: new Big(value)
valueInPercentage: new Big(value)
.div(performanceInformation.performance.currentValue)
.toNumber()
};
@ -437,7 +438,7 @@ export class PortfolioController {
sectors: hasDetails ? portfolioPosition.sectors : [],
symbol: portfolioPosition.symbol,
url: portfolioPosition.url,
value: portfolioPosition.value / totalValue
valueInPercentage: portfolioPosition.value / totalValue
};
}

57
apps/api/src/app/portfolio/portfolio.service.ts

@ -24,7 +24,7 @@ import {
MAX_CHART_ITEMS,
UNKNOWN_KEY
} from '@ghostfolio/common/config';
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper';
import {
Accounts,
EnhancedSymbolProfile,
@ -573,7 +573,6 @@ export class PortfolioService {
const cashPositions = await this.getCashPositions({
cashDetails,
userCurrency,
investment: totalInvestmentInBaseCurrency,
value: filteredValueInBaseCurrency
});
@ -599,7 +598,6 @@ export class PortfolioService {
const cashPositions = await this.getCashPositions({
cashDetails,
userCurrency,
investment: totalInvestmentInBaseCurrency,
value: filteredValueInBaseCurrency
});
@ -680,6 +678,8 @@ export class PortfolioService {
return {
tags,
averagePrice: undefined,
dividendInBaseCurrency: undefined,
feeInBaseCurrency: undefined,
firstBuyDate: undefined,
grossPerformance: undefined,
grossPerformancePercent: undefined,
@ -746,12 +746,23 @@ export class PortfolioService {
averagePrice,
currency,
dataSource,
fee,
firstBuyDate,
marketPrice,
quantity,
transactionCount
} = position;
const dividendInBaseCurrency = getSum(
orders
.filter(({ type }) => {
return type === 'DIVIDEND';
})
.map(({ valueInBaseCurrency }) => {
return new Big(valueInBaseCurrency);
})
);
// Convert investment, gross and net performance to currency of user
const investment = this.exchangeRateDataService.toCurrency(
position.investment?.toNumber(),
@ -785,8 +796,8 @@ export class PortfolioService {
historicalDataArray.push({
averagePrice: orders[0].unitPrice,
date: firstBuyDate,
quantity: orders[0].quantity,
value: orders[0].unitPrice
marketPrice: orders[0].unitPrice,
quantity: orders[0].quantity
});
}
@ -815,9 +826,9 @@ export class PortfolioService {
historicalDataArray.push({
date,
marketPrice,
averagePrice: currentAveragePrice,
quantity: currentQuantity,
value: marketPrice
quantity: currentQuantity
});
maxPrice = Math.max(marketPrice ?? 0, maxPrice);
@ -838,6 +849,12 @@ export class PortfolioService {
tags,
transactionCount,
averagePrice: averagePrice.toNumber(),
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
fee.toNumber(),
SymbolProfile.currency,
userCurrency
),
grossPerformancePercent:
position.grossPerformancePercentage?.toNumber(),
historicalData: historicalDataArray,
@ -894,6 +911,8 @@ export class PortfolioService {
SymbolProfile,
tags,
averagePrice: 0,
dividendInBaseCurrency: 0,
feeInBaseCurrency: 0,
firstBuyDate: undefined,
grossPerformance: undefined,
grossPerformancePercent: undefined,
@ -1209,12 +1228,10 @@ export class PortfolioService {
private async getCashPositions({
cashDetails,
investment,
userCurrency,
value
}: {
cashDetails: CashDetails;
investment: Big;
userCurrency: string;
value: Big;
}) {
@ -1692,6 +1709,14 @@ export class PortfolioService {
userId: string;
withExcludedAccounts?: boolean;
}) {
const ordersOfTypeItem = await this.orderService.getOrders({
filters,
userCurrency,
userId,
withExcludedAccounts,
types: ['ITEM']
});
const accounts: PortfolioDetails['accounts'] = {};
let currentAccounts: (Account & {
@ -1722,10 +1747,18 @@ export class PortfolioService {
});
for (const account of currentAccounts) {
const ordersByAccount = orders.filter(({ accountId }) => {
let ordersByAccount = orders.filter(({ accountId }) => {
return accountId === account.id;
});
const ordersOfTypeItemByAccount = ordersOfTypeItem.filter(
({ accountId }) => {
return accountId === account.id;
}
);
ordersByAccount = ordersByAccount.concat(ordersOfTypeItemByAccount);
accounts[account.id] = {
balance: account.balance,
currency: account.currency,
@ -1745,7 +1778,9 @@ export class PortfolioService {
for (const order of ordersByAccount) {
let currentValueOfSymbolInBaseCurrency =
order.quantity *
portfolioItemsNow[order.SymbolProfile.symbol]?.marketPrice ?? 0;
(portfolioItemsNow[order.SymbolProfile.symbol]?.marketPrice ??
order.unitPrice ??
0);
let originalValueOfSymbolInBaseCurrency =
this.exchangeRateDataService.toCurrency(
order.quantity * order.unitPrice,

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

@ -1,6 +1,4 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { PROPERTY_IS_USER_SIGNUP_ENABLED } from '@ghostfolio/common/config';
import { User, UserSettings } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
@ -31,7 +29,6 @@ import { UserService } from './user.service';
@Controller('user')
export class UserController {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly jwtService: JwtService,
private readonly propertyService: PropertyService,
@Inject(REQUEST) private readonly request: RequestWithUser,

26
apps/api/src/app/user/user.service.ts

@ -97,6 +97,7 @@ export class UserService {
const {
accessToken,
Account,
Analytics,
authChallenge,
createdAt,
id,
@ -107,7 +108,12 @@ export class UserService {
thirdPartyId,
updatedAt
} = await this.prismaService.user.findUnique({
include: { Account: true, Settings: true, Subscription: true },
include: {
Account: true,
Analytics: true,
Settings: true,
Subscription: true
},
where: userWhereUniqueInput
});
@ -121,7 +127,8 @@ export class UserService {
role,
Settings,
thirdPartyId,
updatedAt
updatedAt,
activityCount: Analytics?.activityCount
};
if (user?.Settings) {
@ -154,15 +161,22 @@ export class UserService {
(user.Settings.settings as UserSettings).viewMode = 'DEFAULT';
}
let currentPermissions = getPermissions(user.role);
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
user.subscription =
this.subscriptionService.getSubscription(Subscription);
}
let currentPermissions = getPermissions(user.role);
if (
Analytics?.activityCount % 25 === 0 &&
user.subscription?.type === 'Basic'
) {
currentPermissions.push(permissions.enableSubscriptionInterstitial);
}
if (user.subscription?.type === 'Premium') {
currentPermissions.push(permissions.reportDataGlitch);
if (user.subscription?.type === 'Premium') {
currentPermissions.push(permissions.reportDataGlitch);
}
}
if (this.configurationService.get('ENABLE_FEATURE_READ_ONLY_MODE')) {

1
apps/api/src/interceptors/redact-values-in-response.interceptor.ts

@ -35,6 +35,7 @@ export class RedactValuesInResponseInterceptor<T>
'balanceInBaseCurrency',
'comment',
'convertedBalance',
'dividendInBaseCurrency',
'fee',
'feeInBaseCurrency',
'filteredValueInBaseCurrency',

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

@ -42,7 +42,7 @@ export class ConfigurationService {
MAX_ITEM_IN_CACHE: num({ default: 9999 }),
PORT: port({ default: 3333 }),
RAPID_API_API_KEY: str({ default: '' }),
REDIS_HOST: host({ default: 'localhost' }),
REDIS_HOST: str({ default: 'localhost' }),
REDIS_PASSWORD: str({ default: '' }),
REDIS_PORT: port({ default: 6379 }),
ROOT_URL: str({ default: 'http://localhost:4200' }),

18
apps/client/.browserslistrc

@ -1,18 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

5
apps/client/project.json

@ -65,7 +65,10 @@
"output": "./../assets/"
}
],
"styles": ["apps/client/src/styles.scss"],
"styles": [
"apps/client/src/styles/theme.scss",
"apps/client/src/styles.scss"
],
"scripts": ["node_modules/marked/marked.min.js"],
"vendorChunk": true,
"extractLicenses": false,

3
apps/client/src/app/app-routing.module.ts

@ -222,9 +222,8 @@ const routes: Routes = [
// Preload all lazy loaded modules with the attribute preload === true
{
anchorScrolling: 'enabled',
preloadingStrategy: ModulePreloadService,
preloadingStrategy: ModulePreloadService
// enableTracing: true // <-- debugging purposes only
relativeLinkResolution: 'legacy'
}
)
],

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

@ -1,26 +1,17 @@
import { DOCUMENT } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
OnDestroy,
OnInit
} from '@angular/core';
import { Title } from '@angular/platform-browser';
import {
ActivatedRoute,
NavigationEnd,
PRIMARY_OUTLET,
Router
} from '@angular/router';
import {
primaryColorHex,
secondaryColorHex,
warnColorHex
} from '@ghostfolio/common/config';
import { NavigationEnd, PRIMARY_OUTLET, Router } from '@angular/router';
import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ColorScheme } from '@ghostfolio/common/types';
import { MaterialCssVarsService } from 'angular-material-css-vars';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
@ -52,7 +43,7 @@ export class AppComponent implements OnDestroy, OnInit {
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private materialCssVarsService: MaterialCssVarsService,
@Inject(DOCUMENT) private document: Document,
private router: Router,
private title: Title,
private tokenStorageService: TokenStorageService,
@ -126,16 +117,20 @@ export class AppComponent implements OnDestroy, OnInit {
? userPreferredColorScheme === 'DARK'
: window.matchMedia('(prefers-color-scheme: dark)').matches;
this.materialCssVarsService.setDarkTheme(isDarkTheme);
this.toggleThemeStyleClass(isDarkTheme);
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
if (!this.user?.settings.colorScheme) {
this.materialCssVarsService.setDarkTheme(event.matches);
this.toggleThemeStyleClass(event.matches);
}
});
}
this.materialCssVarsService.setPrimaryColor(primaryColorHex);
this.materialCssVarsService.setAccentColor(secondaryColorHex);
this.materialCssVarsService.setWarnColor(warnColorHex);
private toggleThemeStyleClass(isDarkTheme: boolean) {
if (isDarkTheme) {
this.document.body.classList.add('is-dark-theme');
} else {
this.document.body.classList.remove('is-dark-theme');
}
}
}

16
apps/client/src/app/app.module.ts

@ -1,20 +1,19 @@
import { Platform } from '@angular/cdk/platform';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatChipsModule } from '@angular/material/chips';
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
MatNativeDateModule
} from '@angular/material/core';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete';
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips';
import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar';
import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ServiceWorkerModule } from '@angular/service-worker';
import { MaterialCssVarsModule } from 'angular-material-css-vars';
import { MarkdownModule } from 'ngx-markdown';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe';
@ -25,6 +24,7 @@ import { DateFormats } from './adapter/date-formats';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { GfHeaderModule } from './components/header/header.module';
import { GfSubscriptionInterstitialDialogModule } from './components/subscription-interstitial-dialog/subscription-interstitial-dialog.module';
import { authInterceptorProviders } from './core/auth.interceptor';
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
import { LanguageService } from './core/language.service';
@ -40,15 +40,11 @@ export function NgxStripeFactory(): string {
BrowserAnimationsModule,
BrowserModule,
GfHeaderModule,
GfSubscriptionInterstitialDialogModule,
HttpClientModule,
MarkdownModule.forRoot(),
MatAutocompleteModule,
MatChipsModule,
MaterialCssVarsModule.forRoot({
darkThemeClass: 'is-dark-theme',
isAutoContrast: true,
lightThemeClass: 'is-light-theme'
}),
MatNativeDateModule,
MatSnackBarModule,
MatTooltipModule,

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

@ -7,7 +7,7 @@ import {
OnInit,
Output
} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { Access } from '@ghostfolio/common/interfaces';

6
apps/client/src/app/components/access-table/access-table.module.ts

@ -1,8 +1,8 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatTableModule } from '@angular/material/table';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatLegacyTableModule as MatTableModule } from '@angular/material/legacy-table';
import { AccessTableComponent } from './access-table.component';

5
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts

@ -6,7 +6,10 @@ import {
OnDestroy,
OnInit
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { downloadAsFile } from '@ghostfolio/common/helper';

4
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';

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

@ -9,8 +9,8 @@ import {
Output,
ViewChild
} from '@angular/core';
import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { Account as AccountModel } from '@prisma/client';
import { get } from 'lodash';

8
apps/client/src/app/components/accounts-table/accounts-table.module.ts

@ -1,10 +1,9 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatLegacyTableModule as MatTableModule } from '@angular/material/legacy-table';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { RouterModule } from '@angular/router';
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
import { GfValueModule } from '@ghostfolio/ui/value';
@ -20,7 +19,6 @@ import { AccountsTableComponent } from './accounts-table.component';
GfSymbolIconModule,
GfValueModule,
MatButtonModule,
MatInputModule,
MatMenuModule,
MatSortModule,
MatTableModule,

6
apps/client/src/app/components/admin-jobs/admin-jobs.module.ts

@ -1,9 +1,9 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { AdminJobsComponent } from './admin-jobs.component';

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

@ -7,7 +7,7 @@ import {
OnInit,
Output
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
DATE_FORMAT,

7
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts

@ -6,7 +6,10 @@ import {
OnDestroy
} from '@angular/core';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { Subject, takeUntil } from 'rxjs';
@ -36,7 +39,7 @@ export class MarketDataDetailDialog implements OnDestroy {
this.dateAdapter.setLocale(this.locale);
}
public onCancel(): void {
public onCancel() {
this.dialogRef.close({ withRefresh: false });
}

8
apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts

@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MarketDataDetailDialog } from './market-data-detail-dialog.component';

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

@ -6,9 +6,9 @@ import {
OnInit,
ViewChild
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';

8
apps/client/src/app/components/admin-market-data/admin-market-data.module.ts

@ -1,13 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatLegacyTableModule as MatTableModule } from '@angular/material/legacy-table';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
import { AdminMarketDataComponent } from './admin-market-data.component';
import { GfAssetProfileDialogModule } from './asset-profile-dialog/assset-profile-dialog.module';
import { GfAssetProfileDialogModule } from './asset-profile-dialog/asset-profile-dialog.module';
@NgModule({
declarations: [AdminMarketDataComponent],

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

@ -7,7 +7,10 @@ import {
OnInit
} from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import {

8
apps/client/src/app/components/admin-market-data/asset-profile-dialog/assset-profile-dialog.module.ts → apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.module.ts

@ -2,10 +2,10 @@ import { TextFieldModule } from '@angular/cdk/text-field';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';

2
apps/client/src/app/components/admin-overview/admin-overview.component.ts

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { MatLegacySlideToggleChange as MatSlideToggleChange } from '@angular/material/legacy-slide-toggle';
import { CacheService } from '@ghostfolio/client/services/cache.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';

8
apps/client/src/app/components/admin-overview/admin-overview.module.ts

@ -1,10 +1,10 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle';
import { CacheService } from '@ghostfolio/client/services/cache.service';
import { GfValueModule } from '@ghostfolio/ui/value';

4
apps/client/src/app/components/admin-users/admin-users.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
import { GfValueModule } from '@ghostfolio/ui/value';

2
apps/client/src/app/components/benchmark-comparator/benchmark-comparator.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatSelectModule } from '@angular/material/select';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';

2
apps/client/src/app/components/dialog-footer/dialog-footer.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { DialogFooterComponent } from './dialog-footer.component';

2
apps/client/src/app/components/dialog-header/dialog-header.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { DialogHeaderComponent } from './dialog-header.component';

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

@ -6,7 +6,7 @@ import {
OnChanges,
Output
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { Router } from '@angular/router';
import { LoginWithAccessTokenDialog } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component';
import { DataService } from '@ghostfolio/client/services/data.service';

4
apps/client/src/app/components/header/header.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { RouterModule } from '@angular/router';
import { LoginWithAccessTokenDialogModule } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.module';

2
apps/client/src/app/components/home-holdings/home-holdings.component.ts

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';

4
apps/client/src/app/components/home-holdings/home-holdings.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { RouterModule } from '@angular/router';
import { GfPositionDetailDialogModule } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.module';
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';

8
apps/client/src/app/components/home-summary/home-summary.component.ts

@ -1,9 +1,9 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import {
MatSnackBar,
MatSnackBarRef,
TextOnlySnackBar
} from '@angular/material/snack-bar';
MatLegacySnackBar as MatSnackBar,
MatLegacySnackBarRef as MatSnackBarRef,
LegacyTextOnlySnackBar as TextOnlySnackBar
} from '@angular/material/legacy-snack-bar';
import { Router } from '@angular/router';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';

2
apps/client/src/app/components/home-summary/home-summary.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { RouterModule } from '@angular/router';
import { GfPortfolioSummaryModule } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.module';

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

@ -1,6 +1,9 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { Router } from '@angular/router';
import { InternetIdentityService } from '@ghostfolio/client/services/internet-identity.service';
import {

10
apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.module.ts

@ -2,11 +2,11 @@ import { TextFieldModule } from '@angular/cdk/text-field';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { GfDialogHeaderModule } from '../dialog-header/dialog-header.module';
import { LoginWithAccessTokenDialog } from './login-with-access-token-dialog.component';

15
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts

@ -6,7 +6,10 @@ import {
OnDestroy,
OnInit
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import {
@ -37,6 +40,8 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
public countries: {
[code: string]: { name: string; value: number };
};
public dividendInBaseCurrency: number;
public feeInBaseCurrency: number;
public firstBuyDate: string;
public grossPerformance: number;
public grossPerformancePercent: number;
@ -78,6 +83,8 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
.subscribe(
({
averagePrice,
dividendInBaseCurrency,
feeInBaseCurrency,
firstBuyDate,
grossPerformance,
grossPerformancePercent,
@ -98,7 +105,8 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.averagePrice = averagePrice;
this.benchmarkDataItems = [];
this.countries = {};
this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=Ghostfolio Data Glitch Report&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${SymbolProfile?.symbol}%0DData Source: ${SymbolProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`;
this.dividendInBaseCurrency = dividendInBaseCurrency;
this.feeInBaseCurrency = feeInBaseCurrency;
this.firstBuyDate = firstBuyDate;
this.grossPerformance = grossPerformance;
this.grossPerformancePercent = grossPerformancePercent;
@ -111,7 +119,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
return {
date: historicalDataItem.date,
value: historicalDataItem.value
value: historicalDataItem.marketPrice
};
}
);
@ -123,6 +131,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.netPerformancePercent = netPerformancePercent;
this.orders = orders;
this.quantity = quantity;
this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=Ghostfolio Data Glitch Report&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${SymbolProfile?.symbol}%0DData Source: ${SymbolProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`;
this.sectors = {};
this.SymbolProfile = SymbolProfile;
this.tags = tags.map(({ id, name }) => {

20
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html

@ -119,6 +119,26 @@
>Investment</gf-value
>
</div>
<div class="col-6 mb-3">
<gf-value
i18n
size="medium"
[currency]="data.baseCurrency"
[locale]="data.locale"
[value]="dividendInBaseCurrency"
>Dividend</gf-value
>
</div>
<div class="col-6 mb-3">
<gf-value
i18n
size="medium"
[currency]="data.baseCurrency"
[locale]="data.locale"
[value]="feeInBaseCurrency"
>Fees</gf-value
>
</div>
<div class="col-6 mb-3">
<gf-value
i18n

6
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts

@ -1,8 +1,8 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';

2
apps/client/src/app/components/position/position.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { RouterModule } from '@angular/router';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { GfTrendIndicatorModule } from '@ghostfolio/ui/trend-indicator';

2
apps/client/src/app/components/positions/positions.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
import { GfPositionModule } from '../position/position.module';

4
apps/client/src/app/components/rules/rules.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { GfRuleModule } from '@ghostfolio/client/components/rule/rule.module';
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';

1
apps/client/src/app/components/subscription-interstitial-dialog/interfaces/interfaces.ts

@ -0,0 +1 @@
export interface SubscriptionInterstitialDialogParams {}

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

@ -0,0 +1,25 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { SubscriptionInterstitialDialogParams } from './interfaces/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'd-flex flex-column flex-grow-1 h-100' },
selector: 'gf-subscription-interstitial-dialog',
styleUrls: ['./subscription-interstitial-dialog.scss'],
templateUrl: 'subscription-interstitial-dialog.html'
})
export class SubscriptionInterstitialDialog {
public constructor(
@Inject(MAT_DIALOG_DATA) public data: SubscriptionInterstitialDialogParams,
public dialogRef: MatDialogRef<SubscriptionInterstitialDialog>
) {}
public onCancel() {
this.dialogRef.close({});
}
}

42
apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html

@ -0,0 +1,42 @@
<h1 class="align-items-center d-flex" mat-dialog-title>
<span>Ghostfolio Premium</span>
<gf-premium-indicator class="ml-1"></gf-premium-indicator>
</h1>
<div class="flex-grow-1" mat-dialog-content>
<p class="h5" i18n>
Are you an ambitious investor who needs the full picture?
</p>
<p i18n>
By upgrading to Ghostfolio Premium, you will get these additional features:
</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
<span i18n>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
<span i18n>Performance Benchmarks</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
<span i18n>Allocations</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
<span i18n>FIRE Calculator</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon class="mr-1" name="checkmark-circle-outline"></ion-icon>
<a i18n [routerLink]="['/features']">and more Features...</a>
</li>
</ul>
<p>Refine your personal investment strategy now.</p>
</div>
<div class="justify-content-end" mat-dialog-actions>
<button i18n mat-button (click)="onCancel()">Skip</button>
<a color="primary" mat-flat-button [routerLink]="['/pricing']">
<span i18n>Upgrade Plan</span>
<ion-icon class="ml-1" name="arrow-forward-outline"></ion-icon>
</a>
</div>

21
apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.module.ts

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { RouterModule } from '@angular/router';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
import { SubscriptionInterstitialDialog } from './subscription-interstitial-dialog.component';
@NgModule({
declarations: [SubscriptionInterstitialDialog],
imports: [
CommonModule,
GfPremiumIndicatorModule,
MatButtonModule,
MatDialogModule,
RouterModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfSubscriptionInterstitialDialogModule {}

11
apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.scss

@ -0,0 +1,11 @@
:host {
display: block;
.mat-dialog-content {
max-height: unset;
ion-icon[name='checkmark-circle-outline'] {
color: rgba(var(--palette-accent-500), 1);
}
}
}

2
apps/client/src/app/components/toggle/toggle.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { MatRadioModule } from '@angular/material/radio';
import { MatLegacyRadioModule as MatRadioModule } from '@angular/material/legacy-radio';
import { ToggleComponent } from './toggle.component';

8
apps/client/src/app/core/http-response.interceptor.ts

@ -8,10 +8,10 @@ import {
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
MatSnackBar,
MatSnackBarRef,
TextOnlySnackBar
} from '@angular/material/snack-bar';
MatLegacySnackBar as MatSnackBar,
MatLegacySnackBarRef as MatSnackBarRef,
LegacyTextOnlySnackBar as TextOnlySnackBar
} from '@angular/material/legacy-snack-bar';
import { Router } from '@angular/router';
import { DataService } from '@ghostfolio/client/services/data.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';

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

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { GfValueModule } from '@ghostfolio/ui/value';
import { AboutPageRoutingModule } from './about-page-routing.module';

2
apps/client/src/app/pages/about/changelog/changelog-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { MarkdownModule } from 'ngx-markdown';
import { ChangelogPageRoutingModule } from './changelog-page-routing.module';

16
apps/client/src/app/pages/account/account-page.component.ts

@ -5,16 +5,16 @@ import {
OnInit,
ViewChild
} from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import {
MatSlideToggle,
MatSlideToggleChange
} from '@angular/material/slide-toggle';
MatLegacySlideToggle as MatSlideToggle,
MatLegacySlideToggleChange as MatSlideToggleChange
} from '@angular/material/legacy-slide-toggle';
import {
MatSnackBar,
MatSnackBarRef,
TextOnlySnackBar
} from '@angular/material/snack-bar';
MatLegacySnackBar as MatSnackBar,
MatLegacySnackBarRef as MatSnackBarRef,
LegacyTextOnlySnackBar as TextOnlySnackBar
} from '@angular/material/legacy-snack-bar';
import { ActivatedRoute, Router } from '@angular/router';
import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto';
import { DataService } from '@ghostfolio/client/services/data.service';

14
apps/client/src/app/pages/account/account-page.module.ts

@ -1,13 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle';
import { RouterModule } from '@angular/router';
import { GfPortfolioAccessTableModule } from '@ghostfolio/client/components/access-table/access-table.module';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
@ -31,7 +30,6 @@ import { GfCreateOrUpdateAccessDialogModule } from './create-or-update-access-di
MatCardModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatSlideToggleModule,
ReactiveFormsModule,

7
apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts

@ -4,7 +4,10 @@ import {
Inject,
OnDestroy
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { Subject } from 'rxjs';
import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
@ -26,7 +29,7 @@ export class CreateOrUpdateAccessDialog implements OnDestroy {
ngOnInit() {}
public onCancel(): void {
public onCancel() {
this.dialogRef.close();
}

10
apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.module.ts

@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { CreateOrUpdateAccessDialog } from './create-or-update-access-dialog.component';

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

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';

2
apps/client/src/app/pages/accounts/accounts-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { RouterModule } from '@angular/router';
import { GfAccountDetailDialogModule } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.module';
import { GfAccountsTableModule } from '@ghostfolio/client/components/accounts-table/accounts-table.module';

7
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts

@ -4,7 +4,10 @@ import {
Inject,
OnDestroy
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { Subject } from 'rxjs';
import { DataService } from '../../../services/data.service';
@ -36,7 +39,7 @@ export class CreateOrUpdateAccountDialog implements OnDestroy {
this.platforms = platforms;
}
public onCancel(): void {
public onCancel() {
this.dialogRef.close();
}

12
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.module.ts

@ -1,12 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.component';

8
apps/client/src/app/pages/admin/admin-page.module.ts

@ -1,9 +1,9 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs';
import { GfAdminJobsModule } from '@ghostfolio/client/components/admin-jobs/admin-jobs.module';
import { GfAdminMarketDataModule } from '@ghostfolio/client/components/admin-market-data/admin-market-data.module';
import { GfAdminOverviewModule } from '@ghostfolio/client/components/admin-overview/admin-overview.module';

2
apps/client/src/app/pages/blog/2022/11/black-friday-2022/black-friday-2022-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { RouterModule } from '@angular/router';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';

2
apps/client/src/app/pages/blog/2022/12/the-importance-of-tracking-your-personal-finances/the-importance-of-tracking-your-personal-finances-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { RouterModule } from '@angular/router';
import { TheImportanceOfTrackingYourPersonalFinancesRoutingModule } from './the-importance-of-tracking-your-personal-finances-page-routing.module';

2
apps/client/src/app/pages/blog/blog-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { BlogPageRoutingModule } from './blog-page-routing.module';
import { BlogPageComponent } from './blog-page.component';

2
apps/client/src/app/pages/faq/faq-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { FaqPageRoutingModule } from './faq-page-routing.module';
import { FaqPageComponent } from './faq-page.component';

4
apps/client/src/app/pages/features/features-page.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
import { FeaturesPageRoutingModule } from './features-page-routing.module';

2
apps/client/src/app/pages/home/home-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatTabsModule } from '@angular/material/tabs';
import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs';
import { RouterModule } from '@angular/router';
import { GfHomeHoldingsModule } from '@ghostfolio/client/components/home-holdings/home-holdings.module';
import { GfHomeMarketModule } from '@ghostfolio/client/components/home-market/home-market.module';

8
apps/client/src/app/pages/landing/landing-page.html

@ -154,6 +154,14 @@
title="Sackgeld.com – Apps für ein höheres Sackgeld"
></a>
</div>
<div class="col-md-3 d-flex justify-content-center my-1">
<a
class="d-block logo logo-sourceforge mask"
href="https://sourceforge.net"
target="_blank"
title="SourceForge: The Complete Open-Source and Business Software Platform"
></a>
</div>
<div class="col-md-3 d-flex justify-content-center my-1">
<a
class="d-block logo logo-unraid mask"

4
apps/client/src/app/pages/landing/landing-page.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { RouterModule } from '@angular/router';
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
import { GfLogoModule } from '@ghostfolio/ui/logo';

5
apps/client/src/app/pages/landing/landing-page.scss

@ -79,6 +79,10 @@
mask-image: url('/assets/images/logo-sackgeld.png');
}
&.logo-sourceforge {
mask-image: url('/assets/images/logo-sourceforge.svg');
}
&.logo-unraid {
mask-image: url('/assets/images/logo-unraid.svg');
}
@ -117,6 +121,7 @@
&.logo-alternative-to,
&.logo-privacy-tools,
&.logo-sackgeld,
&.logo-sourceforge,
&.logo-unraid {
background-color: rgba(var(--light-primary-text));
}

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

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';

4
apps/client/src/app/pages/portfolio/activities/activities-page.module.ts

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar';
import { RouterModule } from '@angular/router';
import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';

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

@ -9,9 +9,12 @@ import {
ViewChild
} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatLegacyAutocompleteSelectedEvent as MatAutocompleteSelectedEvent } from '@angular/material/legacy-autocomplete';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';

8
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -18,12 +18,14 @@
</mat-select>
</mat-form-field>
</div>
<div
[ngClass]="{ 'd-none': !activityForm.controls['accountId'].hasValidator(Validators.required) }"
>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Account</mat-label>
<mat-select formControlName="accountId">
<mat-option
*ngIf="!activityForm.controls['accountId'].hasValidator(Validators.required)"
[value]="null"
></mat-option>
<mat-option *ngFor="let account of data.accounts" [value]="account.id"
>{{ account.name }}</mat-option
>

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

@ -1,15 +1,15 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input';
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { GfValueModule } from '@ghostfolio/ui/value';

9
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -6,8 +6,11 @@ import {
OnDestroy
} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef
} from '@angular/material/legacy-dialog';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { DataService } from '@ghostfolio/client/services/data.service';
@ -82,7 +85,7 @@ export class ImportActivitiesDialog implements OnDestroy {
}
}
public onCancel(): void {
public onCancel() {
this.dialogRef.close();
}

8
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.module.ts

@ -1,11 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';

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

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { AccountDetailDialog } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component';
import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces';
@ -21,6 +21,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Market } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
import { Account, AssetClass, DataSource } from '@prisma/client';
import { isNumber } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
@ -339,7 +340,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
dataSource: position.dataSource,
name: position.name,
symbol: prettifySymbol(symbol),
value: position.value
value: isNumber(position.value)
? position.value
: position.valueInPercentage
};
}
@ -357,7 +360,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}
public onAccountChartClicked({ symbol }: UniqueAsset) {
if (symbol) {
if (symbol && symbol !== UNKNOWN_KEY) {
this.router.navigate([], {
queryParams: { accountId: symbol, accountDetailDialog: true }
});

6
apps/client/src/app/pages/portfolio/allocations/allocations-page.module.ts

@ -1,8 +1,8 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar';
import { GfWorldMapChartModule } from '@ghostfolio/client/components/world-map-chart/world-map-chart.module';
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';

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

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
@ -19,7 +19,7 @@ import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n';
import { AssetClass, DataSource, SymbolProfile } from '@prisma/client';
import { differenceInDays } from 'date-fns';
import { sortBy } from 'lodash';
import { isNumber, sortBy } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
@ -312,12 +312,13 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
date,
netPerformanceInPercentage,
totalInvestment,
value
value,
valueInPercentage
} of chart) {
this.investments.push({ date, investment: totalInvestment });
this.performanceDataItems.push({
date,
value
value: isNumber(value) ? value : valueInPercentage
});
this.performanceDataItemsInPercentage.push({
date,

2
apps/client/src/app/pages/portfolio/analysis/analysis-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card';
import { GfBenchmarkComparatorModule } from '@ghostfolio/client/components/benchmark-comparator/benchmark-comparator.module';
import { GfInvestmentChartModule } from '@ghostfolio/client/components/investment-chart/investment-chart.module';
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';

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

@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { PositionDetailDialogParams } from '@ghostfolio/client/components/position/position-detail-dialog/interfaces/interfaces';
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';

2
apps/client/src/app/pages/portfolio/holdings/holdings-page.module.ts

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
import { GfHoldingsTableModule } from '@ghostfolio/ui/holdings-table/holdings-table.module';

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

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatTabsModule } from '@angular/material/tabs';
import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs';
import { RouterModule } from '@angular/router';
import { PortfolioPageRoutingModule } from './portfolio-page-routing.module';

171
apps/client/src/app/pages/pricing/pricing-page.html

@ -31,50 +31,71 @@
<mat-card class="d-flex flex-column h-100">
<div class="flex-grow-1">
<h4>Open Source</h4>
<p>
For tech-savvy investors who prefer to run
<strong>Ghostfolio</strong> on their own infrastructure.
<p i18n>
For tech-savvy investors who prefer to run Ghostfolio on their
own infrastructure.
</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
<span i18n>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
<span i18n>Unlimited Accounts</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Zen Mode</span>
<span i18n>Portfolio Performance</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Summary</span>
<span i18n>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Advanced Insights</span>
<span i18n>Portfolio Allocations</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span i18n>Performance Benchmarks</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span i18n>FIRE Calculator</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<a i18n [routerLink]="['/features']">and more Features...</a>
</li>
</ul>
</div>
<p>Self-hosted, update manually.</p>
<p class="h5 text-right">Free</p>
<p i18n>Self-hosted, update manually.</p>
<p class="h5 text-right" i18n>Free</p>
<div
*ngIf="user?.subscription?.type === 'Basic'"
class="d-none d-lg-block hidden mt-3 text-center"
@ -92,31 +113,54 @@
[ngClass]="{ 'active': user?.subscription?.type === 'Basic' }"
>
<div class="flex-grow-1">
<h4 class="align-items-center d-flex">Basic</h4>
<p>
<div class="align-items-center d-flex mb-2">
<h4 class="flex-grow-1 m-0">Basic</h4>
<div *ngIf="user?.subscription?.type === 'Basic'">
<ion-icon class="mr-1" name="checkmark-outline"></ion-icon>
</div>
</div>
<p i18n>
For new investors who are just getting started with trading.
</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
<span i18n>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
<span i18n>Unlimited Accounts</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span i18n>Portfolio Performance</span>
</li>
<li>
<ion-icon
class="invisible"
name="checkmark-circle-outline"
></ion-icon>
</li>
<li>
<ion-icon
class="invisible"
name="checkmark-circle-outline"
></ion-icon>
</li>
<li>
<ion-icon
class="invisible"
name="checkmark-circle-outline"
></ion-icon>
<span>Zen Mode</span>
</li>
<li>
<ion-icon
@ -132,8 +176,8 @@
</li>
</ul>
</div>
<p>Fully managed <strong>Ghostfolio</strong> cloud offering.</p>
<p class="h5 text-right">Free</p>
<p i18n>Fully managed Ghostfolio cloud offering.</p>
<p class="h5 text-right" i18n>Free</p>
<div
*ngIf="user?.subscription?.type === 'Basic'"
class="d-none d-lg-block hidden mt-3 text-center"
@ -151,56 +195,82 @@
[ngClass]="{ 'active': user?.subscription?.type === 'Premium' }"
>
<div class="flex-grow-1">
<h4 class="align-items-center d-flex">
<span>Premium</span>
<gf-premium-indicator
class="ml-1"
[enableLink]="false"
></gf-premium-indicator>
</h4>
<p>
<div class="align-items-center d-flex mb-2">
<h4 class="align-items-center d-flex flex-grow-1 m-0">
<span>Premium</span>
<gf-premium-indicator
class="ml-1"
[enableLink]="false"
></gf-premium-indicator>
</h4>
<div *ngIf="user?.subscription?.type === 'Premium'">
<ion-icon class="mr-1" name="checkmark-outline"></ion-icon>
</div>
</div>
<p i18n>
For ambitious investors who need the full picture of their
financial assets.
</p>
<ul class="list-unstyled mb-3">
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span i18n>Unlimited Transactions</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span i18n>Unlimited Accounts</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Unlimited Transactions</span>
<span i18n>Portfolio Performance</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Performance</span>
<span i18n>Portfolio Summary</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Zen Mode</span>
<span i18n>Portfolio Allocations</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Portfolio Summary</span>
<span i18n>Performance Benchmarks</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1 text-muted"
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<span>Advanced Insights</span>
<span i18n>FIRE Calculator</span>
</li>
<li class="align-items-center d-flex mb-1">
<ion-icon
class="mr-1"
name="checkmark-circle-outline"
></ion-icon>
<a i18n [routerLink]="['/features']">and more Features...</a>
</li>
</ul>
</div>
<p>Fully managed <strong>Ghostfolio</strong> cloud offering.</p>
<p i18n>Fully managed Ghostfolio cloud offering.</p>
<p class="h5 text-right" [hidden]="!price">
<span class="font-weight-normal">
<ng-container *ngIf="coupon"
@ -221,11 +291,16 @@
*ngIf="user?.subscription?.type === 'Basic'"
class="mt-3 text-center"
>
<a color="primary" mat-flat-button [routerLink]="['/account']">
<a
color="primary"
i18n
mat-flat-button
[routerLink]="['/account']"
>
Upgrade Plan
</a>
<p class="m-0 text-muted">
<small>One-time payment, no auto-renewal.</small>
<small i18n>One-time payment, no auto-renewal.</small>
</p>
</div>
</mat-card>
@ -235,10 +310,10 @@
</div>
<div *ngIf="!user" class="row">
<div class="col mt-3 text-center">
<a color="primary" mat-flat-button [routerLink]="['/register']">
<a color="primary" i18n mat-flat-button [routerLink]="['/register']">
Get Started
</a>
<p class="m-0 text-muted"><small>It’s free.</small></p>
<p class="m-0 text-muted"><small i18n>It’s free.</small></p>
</div>
</div>
</div>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save