Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
20195b2b1a | |||
7fa4e6ebd2 | |||
d8531ddfcb | |||
70d670b711 | |||
27b0663a80 | |||
874dfb0235 | |||
072db0d558 | |||
12e692429a | |||
e22b8b78b8 | |||
dc5052f7dc | |||
335553e891 | |||
d480ad1023 | |||
7320751056 | |||
108c0c13c4 | |||
053a5cc5b5 | |||
c456a8bcfe | |||
6fcecb5bc6 | |||
e4e0a7d9f0 | |||
c7173761a3 | |||
185e130d9f | |||
81245635af | |||
55182ac1af |
28
CHANGELOG.md
28
CHANGELOG.md
@ -5,6 +5,34 @@ 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).
|
||||
|
||||
## 1.180.0 - 18.08.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Set up `ng-extract-i18n-merge` to improve the i18n extraction and merge workflow
|
||||
- Set up language localization for German (`de`)
|
||||
- Resolved the feature graphic of the blog post
|
||||
|
||||
### Changed
|
||||
|
||||
- Tagged template literal strings in components for localization with `$localize`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the license component in the about page
|
||||
- Fixed the links to the blog posts
|
||||
|
||||
## 1.179.5 - 15.08.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Set up i18n support
|
||||
- Added a blog post: _500 Stars on GitHub_
|
||||
|
||||
### Changed
|
||||
|
||||
- Reduced the maximum width of the performance chart on the home page
|
||||
|
||||
## 1.178.0 - 09.08.2022
|
||||
|
||||
### Added
|
||||
|
43
angular.json
43
angular.json
@ -77,41 +77,45 @@
|
||||
"polyfills": "apps/client/src/polyfills.ts",
|
||||
"tsConfig": "apps/client/tsconfig.app.json",
|
||||
"assets": [
|
||||
"apps/client/src/assets",
|
||||
{
|
||||
"glob": "assetlinks.json",
|
||||
"input": "apps/client/src/assets",
|
||||
"output": "./.well-known"
|
||||
"output": "./../.well-known"
|
||||
},
|
||||
{
|
||||
"glob": "CHANGELOG.md",
|
||||
"input": "",
|
||||
"output": "./assets"
|
||||
"output": "./../assets"
|
||||
},
|
||||
{
|
||||
"glob": "LICENSE",
|
||||
"input": "",
|
||||
"output": "./assets"
|
||||
"output": "./../assets"
|
||||
},
|
||||
{
|
||||
"glob": "robots.txt",
|
||||
"input": "apps/client/src/assets",
|
||||
"output": "./"
|
||||
"output": "./../"
|
||||
},
|
||||
{
|
||||
"glob": "sitemap.xml",
|
||||
"input": "apps/client/src/assets",
|
||||
"output": "./"
|
||||
"output": "./../"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/ionicons/dist/ionicons",
|
||||
"output": "./ionicons"
|
||||
"output": "./../ionicons"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.js",
|
||||
"input": "node_modules/ionicons/dist/",
|
||||
"output": "./"
|
||||
"output": "./../"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/client/src/assets",
|
||||
"output": "./../assets/"
|
||||
}
|
||||
],
|
||||
"styles": ["apps/client/src/styles.scss"],
|
||||
@ -124,6 +128,10 @@
|
||||
"namedChunks": true
|
||||
},
|
||||
"configurations": {
|
||||
"development-en": {
|
||||
"baseHref": "/en/",
|
||||
"localize": ["en"]
|
||||
},
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
@ -162,15 +170,21 @@
|
||||
"proxyConfig": "apps/client/proxy.conf.json"
|
||||
},
|
||||
"configurations": {
|
||||
"development-en": {
|
||||
"browserTarget": "client:build:development-en"
|
||||
},
|
||||
"production": {
|
||||
"browserTarget": "client:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"builder": "ng-extract-i18n-merge:ng-extract-i18n-merge",
|
||||
"options": {
|
||||
"browserTarget": "client:build"
|
||||
"browserTarget": "client:build",
|
||||
"includeContext": true,
|
||||
"outputPath": "src/locales",
|
||||
"targetFiles": ["messages.de.xlf"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
@ -188,6 +202,15 @@
|
||||
"outputs": ["coverage/apps/client"]
|
||||
}
|
||||
},
|
||||
"i18n": {
|
||||
"locales": {
|
||||
"de": {
|
||||
"baseHref": "/de/",
|
||||
"translation": "apps/client/src/locales/messages.de.xlf"
|
||||
}
|
||||
},
|
||||
"sourceLocale": "en"
|
||||
},
|
||||
"tags": []
|
||||
},
|
||||
"client-e2e": {
|
||||
|
@ -10,7 +10,7 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d
|
||||
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 { Module } from '@nestjs/common';
|
||||
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||
@ -23,6 +23,7 @@ import { AuthModule } from './auth/auth.module';
|
||||
import { BenchmarkModule } from './benchmark/benchmark.module';
|
||||
import { CacheModule } from './cache/cache.module';
|
||||
import { ExportModule } from './export/export.module';
|
||||
import { FrontendMiddleware } from './frontend.middleware';
|
||||
import { ImportModule } from './import/import.module';
|
||||
import { InfoModule } from './info/info.module';
|
||||
import { OrderModule } from './order/order.module';
|
||||
@ -82,4 +83,10 @@ import { UserModule } from './user/user.module';
|
||||
controllers: [AppController],
|
||||
providers: [CronService]
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(FrontendMiddleware)
|
||||
.forRoutes({ path: '*', method: RequestMethod.ALL });
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||
import { OAuthResponse } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
Body,
|
||||
@ -62,9 +63,17 @@ export class AuthController {
|
||||
const jwt: string = req.user.jwt;
|
||||
|
||||
if (jwt) {
|
||||
res.redirect(`${this.configurationService.get('ROOT_URL')}/auth/${jwt}`);
|
||||
res.redirect(
|
||||
`${this.configurationService.get(
|
||||
'ROOT_URL'
|
||||
)}/${DEFAULT_LANGUAGE_CODE}/auth/${jwt}`
|
||||
);
|
||||
} else {
|
||||
res.redirect(`${this.configurationService.get('ROOT_URL')}/auth`);
|
||||
res.redirect(
|
||||
`${this.configurationService.get(
|
||||
'ROOT_URL'
|
||||
)}/${DEFAULT_LANGUAGE_CODE}/auth`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
53
apps/api/src/app/frontend.middleware.ts
Normal file
53
apps/api/src/app/frontend.middleware.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import * as path from 'path';
|
||||
|
||||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class FrontendMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: NextFunction) {
|
||||
if (req.url.includes('cover.png')) {
|
||||
Logger.log(`Referer: ${req.headers.referer}`, 'FrontendMiddleware');
|
||||
|
||||
// Resolve feature graphic for blog post
|
||||
if (req.headers.referer?.includes('500-stars-on-github')) {
|
||||
res.sendFile(
|
||||
path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'client',
|
||||
'assets',
|
||||
'images',
|
||||
'blog',
|
||||
'500-stars-on-github.jpg'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Skip
|
||||
next();
|
||||
}
|
||||
} else if (req.path.startsWith('/api/') || this.isFileRequest(req.url)) {
|
||||
// Skip
|
||||
next();
|
||||
} else if (req.path.startsWith('/de/')) {
|
||||
res.sendFile(this.getPathOfIndexHtmlFile('de'));
|
||||
} else {
|
||||
res.sendFile(this.getPathOfIndexHtmlFile(DEFAULT_LANGUAGE_CODE));
|
||||
}
|
||||
}
|
||||
|
||||
private getPathOfIndexHtmlFile(aLocale: string) {
|
||||
return path.join(__dirname, '..', 'client', aLocale, 'index.html');
|
||||
}
|
||||
|
||||
private isFileRequest(filename: string) {
|
||||
if (filename === '/assets/LICENSE') {
|
||||
return true;
|
||||
} else if (filename.includes('auth/ey')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filename.split('.').pop() !== filename;
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||
import { PROPERTY_COUPONS } from '@ghostfolio/common/config';
|
||||
import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
PROPERTY_COUPONS
|
||||
} from '@ghostfolio/common/config';
|
||||
import { Coupon } from '@ghostfolio/common/interfaces';
|
||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||
import {
|
||||
@ -93,7 +96,11 @@ export class SubscriptionController {
|
||||
'SubscriptionController'
|
||||
);
|
||||
|
||||
res.redirect(`${this.configurationService.get('ROOT_URL')}/account`);
|
||||
res.redirect(
|
||||
`${this.configurationService.get(
|
||||
'ROOT_URL'
|
||||
)}/${DEFAULT_LANGUAGE_CODE}/account`
|
||||
);
|
||||
}
|
||||
|
||||
@Post('stripe/checkout-session')
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Subscription } from '@prisma/client';
|
||||
@ -33,7 +34,9 @@ export class SubscriptionService {
|
||||
userId: string;
|
||||
}) {
|
||||
const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = {
|
||||
cancel_url: `${this.configurationService.get('ROOT_URL')}/account`,
|
||||
cancel_url: `${this.configurationService.get(
|
||||
'ROOT_URL'
|
||||
)}/${DEFAULT_LANGUAGE_CODE}/account`,
|
||||
client_reference_id: userId,
|
||||
line_items: [
|
||||
{
|
||||
|
@ -54,45 +54,52 @@ const routes: Routes = [
|
||||
import('./pages/blog/blog-page.module').then((m) => m.BlogPageModule)
|
||||
},
|
||||
{
|
||||
path: 'de/blog/2021/07/hallo-ghostfolio',
|
||||
path: 'blog/2021/07/hallo-ghostfolio',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.module'
|
||||
).then((m) => m.HalloGhostfolioPageModule)
|
||||
},
|
||||
{
|
||||
path: 'demo',
|
||||
loadChildren: () =>
|
||||
import('./pages/demo/demo-page.module').then((m) => m.DemoPageModule)
|
||||
},
|
||||
{
|
||||
path: 'en/blog/2021/07/hello-ghostfolio',
|
||||
path: 'blog/2021/07/hello-ghostfolio',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2021/07/hello-ghostfolio/hello-ghostfolio-page.module'
|
||||
).then((m) => m.HelloGhostfolioPageModule)
|
||||
},
|
||||
{
|
||||
path: 'en/blog/2022/01/ghostfolio-first-months-in-open-source',
|
||||
path: 'blog/2022/01/ghostfolio-first-months-in-open-source',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2022/01/first-months-in-open-source/first-months-in-open-source-page.module'
|
||||
).then((m) => m.FirstMonthsInOpenSourcePageModule)
|
||||
},
|
||||
{
|
||||
path: 'en/blog/2022/07/ghostfolio-meets-internet-identity',
|
||||
path: 'blog/2022/07/ghostfolio-meets-internet-identity',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2022/07/ghostfolio-meets-internet-identity/ghostfolio-meets-internet-identity-page.module'
|
||||
).then((m) => m.GhostfolioMeetsInternetIdentityPageModule)
|
||||
},
|
||||
{
|
||||
path: 'en/blog/2022/07/how-do-i-get-my-finances-in-order',
|
||||
path: 'blog/2022/07/how-do-i-get-my-finances-in-order',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2022/07/how-do-i-get-my-finances-in-order/how-do-i-get-my-finances-in-order-page.module'
|
||||
).then((m) => m.HowDoIGetMyFinancesInOrderPageModule)
|
||||
},
|
||||
{
|
||||
path: 'blog/2022/08/500-stars-on-github',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.module'
|
||||
).then((m) => m.FiveHundredStarsOnGitHubPageModule)
|
||||
},
|
||||
{
|
||||
path: 'demo',
|
||||
loadChildren: () =>
|
||||
import('./pages/demo/demo-page.module').then((m) => m.DemoPageModule)
|
||||
},
|
||||
{
|
||||
path: 'faq',
|
||||
loadChildren: () =>
|
||||
|
@ -21,8 +21,10 @@
|
||||
<td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
|
||||
<ng-container *ngIf="element.type === 'PUBLIC'">
|
||||
<ion-icon class="mr-1" name="link-outline"></ion-icon>
|
||||
<a href="{{ baseUrl }}/p/{{ element.id }}" target="_blank"
|
||||
>{{ baseUrl }}/p/{{ element.id }}</a
|
||||
<a
|
||||
href="{{ baseUrl }}/{{ defaultLanguageCode }}/p/{{ element.id }}"
|
||||
target="_blank"
|
||||
>{{ baseUrl }}/{{ defaultLanguageCode }}/p/{{ element.id }}</a
|
||||
>
|
||||
</ng-container>
|
||||
</td>
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||
import { Access } from '@ghostfolio/common/interfaces';
|
||||
|
||||
@Component({
|
||||
@ -24,6 +25,7 @@ export class AccessTableComponent implements OnChanges, OnInit {
|
||||
|
||||
public baseUrl = window.location.origin;
|
||||
public dataSource: MatTableDataSource<Access>;
|
||||
public defaultLanguageCode = DEFAULT_LANGUAGE_CODE;
|
||||
public displayedColumns = [];
|
||||
|
||||
public constructor() {}
|
||||
@ -44,7 +46,7 @@ export class AccessTableComponent implements OnChanges, OnInit {
|
||||
|
||||
public onDeleteAccess(aId: string) {
|
||||
const confirmation = confirm(
|
||||
'Do you really want to revoke this granted access?'
|
||||
$localize`Do you really want to revoke this granted access?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
|
@ -69,7 +69,9 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onDeleteAccount(aId: string) {
|
||||
const confirmation = confirm('Do you really want to delete this account?');
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to delete this account?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
this.accountDeleted.emit(aId);
|
||||
|
@ -103,7 +103,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onAddCurrency() {
|
||||
const currency = prompt('Please add a currency:');
|
||||
const currency = prompt($localize`Please add a currency:`);
|
||||
|
||||
if (currency) {
|
||||
const currencies = uniq([...this.customCurrencies, currency]);
|
||||
@ -116,7 +116,9 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onDeleteCoupon(aCouponCode: string) {
|
||||
const confirmation = confirm('Do you really want to delete this coupon?');
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to delete this coupon?`
|
||||
);
|
||||
|
||||
if (confirmation === true) {
|
||||
const coupons = this.coupons.filter((coupon) => {
|
||||
@ -127,7 +129,9 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onDeleteCurrency(aCurrency: string) {
|
||||
const confirmation = confirm('Do you really want to delete this currency?');
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to delete this currency?`
|
||||
);
|
||||
|
||||
if (confirmation === true) {
|
||||
const currencies = this.customCurrencies.filter((currency) => {
|
||||
@ -142,7 +146,9 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onFlushCache() {
|
||||
const confirmation = confirm('Do you really want to flush the cache?');
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to flush the cache?`
|
||||
);
|
||||
|
||||
if (confirmation === true) {
|
||||
this.cacheService
|
||||
@ -190,7 +196,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onSetSystemMessage() {
|
||||
const systemMessage = prompt('Please set your system message:');
|
||||
const systemMessage = prompt($localize`Please set your system message:`);
|
||||
|
||||
if (systemMessage) {
|
||||
this.putSystemMessage(systemMessage);
|
||||
|
@ -55,7 +55,9 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onDeleteUser(aId: string) {
|
||||
const confirmation = confirm('Do you really want to delete this user?');
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to delete this user?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
this.dataService
|
||||
|
@ -109,7 +109,7 @@ export class HeaderComponent implements OnChanges {
|
||||
data: {
|
||||
accessToken: '',
|
||||
hasPermissionToUseSocialLogin: this.hasPermissionForSocialLogin,
|
||||
title: 'Sign in'
|
||||
title: $localize`Sign in`
|
||||
},
|
||||
width: '30rem'
|
||||
});
|
||||
@ -123,7 +123,7 @@ export class HeaderComponent implements OnChanges {
|
||||
.loginAnonymous(data?.accessToken)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
alert('Oops! Incorrect Security Token.');
|
||||
alert($localize`Oops! Incorrect Security Token.`);
|
||||
|
||||
return EMPTY;
|
||||
}),
|
||||
|
@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/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';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||
import {
|
||||
@ -9,7 +10,6 @@ import {
|
||||
SettingsStorageService
|
||||
} from '@ghostfolio/client/services/settings-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { defaultDateRangeOptions } from '@ghostfolio/common/config';
|
||||
import { Position, User } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { DateRange } from '@ghostfolio/common/types';
|
||||
@ -27,7 +27,7 @@ import { PositionDetailDialogParams } from '../position/position-detail-dialog/i
|
||||
})
|
||||
export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
||||
public dateRange: DateRange;
|
||||
public dateRangeOptions = defaultDateRangeOptions;
|
||||
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
||||
public deviceType: string;
|
||||
public hasImpersonationId: boolean;
|
||||
public hasPermissionToCreateOrder: boolean;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||
import {
|
||||
@ -6,7 +7,6 @@ import {
|
||||
SettingsStorageService
|
||||
} from '@ghostfolio/client/services/settings-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { defaultDateRangeOptions } from '@ghostfolio/common/config';
|
||||
import {
|
||||
PortfolioPerformance,
|
||||
UniqueAsset,
|
||||
@ -26,7 +26,7 @@ import { takeUntil } from 'rxjs/operators';
|
||||
})
|
||||
export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||
public dateRange: DateRange;
|
||||
public dateRangeOptions = defaultDateRangeOptions;
|
||||
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
||||
public deviceType: string;
|
||||
public errors: UniqueAsset[];
|
||||
public hasError: boolean;
|
||||
|
@ -6,7 +6,7 @@
|
||||
.chart-container {
|
||||
aspect-ratio: 16 / 9;
|
||||
height: auto;
|
||||
max-width: 67rem;
|
||||
max-width: 50rem;
|
||||
|
||||
// Fallback for aspect-ratio (using padding hack)
|
||||
@supports not (aspect-ratio: 16 / 9) {
|
||||
|
@ -25,14 +25,14 @@
|
||||
>
|
||||
<img
|
||||
class="mr-2"
|
||||
src="./assets/icons/internet-computer.svg"
|
||||
src="../assets/icons/internet-computer.svg"
|
||||
style="height: 0.75rem"
|
||||
/><span i18n>Sign in with Internet Identity</span>
|
||||
</button>
|
||||
<a href="/api/v1/auth/google" mat-stroked-button
|
||||
<a href="../api/v1/auth/google" mat-stroked-button
|
||||
><img
|
||||
class="mr-2"
|
||||
src="./assets/icons/google.svg"
|
||||
src="../assets/icons/google.svg"
|
||||
style="height: 1rem"
|
||||
/><span i18n>Sign in with Google</span></a
|
||||
>
|
||||
|
@ -45,7 +45,7 @@ export class PortfolioSummaryComponent implements OnChanges, OnInit {
|
||||
|
||||
public onEditEmergencyFund() {
|
||||
const emergencyFundInput = prompt(
|
||||
'Please enter the amount of your emergency fund:',
|
||||
$localize`Please enter the amount of your emergency fund:`,
|
||||
this.summary.emergencyFund.toString()
|
||||
);
|
||||
const emergencyFund = parseFloat(emergencyFundInput?.trim());
|
||||
|
@ -17,6 +17,14 @@ import { ToggleOption } from '@ghostfolio/common/types';
|
||||
styleUrls: ['./toggle.component.scss']
|
||||
})
|
||||
export class ToggleComponent implements OnChanges, OnInit {
|
||||
public static DEFAULT_DATE_RANGE_OPTIONS: ToggleOption[] = [
|
||||
{ label: $localize`Today`, value: '1d' },
|
||||
{ label: $localize`YTD`, value: 'ytd' },
|
||||
{ label: $localize`1Y`, value: '1y' },
|
||||
{ label: $localize`5Y`, value: '5y' },
|
||||
{ label: $localize`Max`, value: 'max' }
|
||||
];
|
||||
|
||||
@Input() defaultValue: string;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() options: ToggleOption[];
|
||||
|
@ -56,14 +56,16 @@ export class HttpResponseInterceptor implements HttpInterceptor {
|
||||
if (!this.snackBarRef) {
|
||||
if (this.info.isReadOnlyMode) {
|
||||
this.snackBarRef = this.snackBar.open(
|
||||
'This feature is currently unavailable. Please try again later.',
|
||||
$localize`This feature is currently unavailable. Please try again later.`,
|
||||
undefined,
|
||||
{ duration: 6000 }
|
||||
);
|
||||
} else {
|
||||
this.snackBarRef = this.snackBar.open(
|
||||
'This feature requires a subscription.',
|
||||
this.hasPermissionForSubscription ? 'Upgrade Plan' : undefined,
|
||||
$localize`This feature requires a subscription.`,
|
||||
this.hasPermissionForSubscription
|
||||
? $localize`Upgrade Plan`
|
||||
: undefined,
|
||||
{ duration: 6000 }
|
||||
);
|
||||
}
|
||||
@ -79,8 +81,8 @@ export class HttpResponseInterceptor implements HttpInterceptor {
|
||||
} else if (error.status === StatusCodes.INTERNAL_SERVER_ERROR) {
|
||||
if (!this.snackBarRef) {
|
||||
this.snackBarRef = this.snackBar.open(
|
||||
'Oops! Something went wrong. Please try again later.',
|
||||
'Okay',
|
||||
$localize`Oops! Something went wrong. Please try again later.`,
|
||||
$localize`Okay`,
|
||||
{ duration: 6000 }
|
||||
);
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: AboutPageComponent,
|
||||
path: '',
|
||||
title: 'About'
|
||||
title: $localize`About`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: ChangelogPageComponent,
|
||||
path: '',
|
||||
title: 'Changelog & License'
|
||||
title: $localize`Changelog & License`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<h3 class="mb-3 text-center" i18n>Changelog</h3>
|
||||
<mat-card class="changelog">
|
||||
<mat-card-content>
|
||||
<markdown [src]="'assets/CHANGELOG.md'"></markdown>
|
||||
<markdown [src]="'../assets/CHANGELOG.md'"></markdown>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@ -15,7 +15,7 @@
|
||||
<h3 class="mb-3 text-center" i18n>License</h3>
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<markdown [src]="'assets/LICENSE'"></markdown>
|
||||
<markdown [src]="'../assets/LICENSE'"></markdown>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: PrivacyPolicyPageComponent,
|
||||
path: '',
|
||||
title: 'Privacy Policy'
|
||||
title: $localize`Privacy Policy`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h3 class="mb-3 text-center" i18n>Privacy Policy</h3>
|
||||
<markdown [src]="'assets/privacy-policy.md'"></markdown>
|
||||
<markdown [src]="'../assets/privacy-policy.md'"></markdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: AccountPageComponent,
|
||||
path: '',
|
||||
title: 'My Ghostfolio'
|
||||
title: $localize`My Ghostfolio`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -218,7 +218,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onRedeemCoupon() {
|
||||
let couponCode = prompt('Please enter your coupon code:');
|
||||
let couponCode = prompt($localize`Please enter your coupon code:`);
|
||||
couponCode = couponCode?.trim();
|
||||
|
||||
if (couponCode) {
|
||||
@ -227,17 +227,21 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
||||
.pipe(
|
||||
takeUntil(this.unsubscribeSubject),
|
||||
catchError(() => {
|
||||
this.snackBar.open('😞 Could not redeem coupon code', undefined, {
|
||||
duration: 3000
|
||||
});
|
||||
this.snackBar.open(
|
||||
'😞 ' + $localize`Could not redeem coupon code`,
|
||||
undefined,
|
||||
{
|
||||
duration: 3000
|
||||
}
|
||||
);
|
||||
|
||||
return EMPTY;
|
||||
})
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.snackBarRef = this.snackBar.open(
|
||||
'✅ Coupon code has been redeemed',
|
||||
'Reload',
|
||||
'✅' + $localize`Coupon code has been redeemed`,
|
||||
$localize`Reload`,
|
||||
{
|
||||
duration: 3000
|
||||
}
|
||||
@ -283,7 +287,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
||||
this.registerDevice();
|
||||
} else {
|
||||
const confirmation = confirm(
|
||||
'Do you really want to remove this sign in method?'
|
||||
$localize`Do you really want to remove this sign in method?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: AccountsPageComponent,
|
||||
path: '',
|
||||
title: 'Accounts'
|
||||
title: $localize`Accounts`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -20,7 +20,7 @@ const routes: Routes = [
|
||||
],
|
||||
component: AdminPageComponent,
|
||||
path: '',
|
||||
title: 'Admin Control'
|
||||
title: $localize`Admin Control`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -28,6 +28,7 @@ export class AuthPageComponent implements OnDestroy, OnInit {
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((params) => {
|
||||
const jwt = params['jwt'];
|
||||
|
||||
this.tokenStorageService.saveToken(
|
||||
jwt,
|
||||
this.settingsStorageService.getSetting(STAY_SIGNED_IN) === 'true'
|
||||
|
@ -68,7 +68,7 @@
|
||||
<p class="my-5 text-center">
|
||||
<img
|
||||
alt="Ghostfol.io Screenshot"
|
||||
src="./assets/images/screenshot.png"
|
||||
src="../assets/images/screenshot.png"
|
||||
style="max-width: 100%; width: 20rem"
|
||||
title="Ghostfol.io Screenshot"
|
||||
/>
|
||||
|
@ -66,7 +66,7 @@
|
||||
<p class="my-5 text-center">
|
||||
<img
|
||||
alt="Ghostfol.io Screenshot"
|
||||
src="./assets/images/screenshot.png"
|
||||
src="../assets/images/screenshot.png"
|
||||
style="max-width: 100%; width: 20rem"
|
||||
title="Ghostfol.io Screenshot"
|
||||
/>
|
||||
|
@ -20,9 +20,7 @@
|
||||
<h2 class="h4">From 1* to 100 stars on GitHub</h2>
|
||||
<p>
|
||||
When I decided to
|
||||
<a [routerLink]="['/en', 'blog', '2021', '07', 'hello-ghostfolio']"
|
||||
>publish</a
|
||||
>
|
||||
<a href="../en/blog/2021/07/hello-ghostfolio">publish</a>
|
||||
the project as
|
||||
<a href="https://github.com/ghostfolio/ghostfolio"
|
||||
>open source software</a
|
||||
|
@ -7,8 +7,8 @@
|
||||
<div class="mb-3 text-muted"><small>2022-07-23</small></div>
|
||||
<img
|
||||
alt="Ghostfolio meets Internet Identity Teaser"
|
||||
class="w-100"
|
||||
src="./assets/images/blog/ghostfolio-meets-internet-identity.png"
|
||||
class="rounded w-100"
|
||||
src="../assets/images/blog/ghostfolio-meets-internet-identity.png"
|
||||
title="Ghostfolio meets Internet Identity"
|
||||
/>
|
||||
</div>
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { FiveHundredStarsOnGitHubPageComponent } from './500-stars-on-github-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
canActivate: [AuthGuard],
|
||||
component: FiveHundredStarsOnGitHubPageComponent,
|
||||
path: '',
|
||||
title: '500 Stars on GitHub'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FiveHundredStarsOnGitHubRoutingModule {}
|
@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
selector: 'gf-500-stars-on-github-page',
|
||||
styleUrls: ['./500-stars-on-github-page.scss'],
|
||||
templateUrl: './500-stars-on-github-page.html'
|
||||
})
|
||||
export class FiveHundredStarsOnGitHubPageComponent {}
|
@ -0,0 +1,195 @@
|
||||
<div class="blog container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<article>
|
||||
<div class="mb-4 text-center">
|
||||
<h1 class="mb-1">500 Stars</h1>
|
||||
<div class="mb-3 text-muted"><small>2022-08-18</small></div>
|
||||
<img
|
||||
alt="500 Stars on GitHub Teaser"
|
||||
class="rounded w-100"
|
||||
src="../assets/images/blog/500-stars-on-github.jpg"
|
||||
title="500 Stars on GitHub"
|
||||
/>
|
||||
</div>
|
||||
<section class="mb-4">
|
||||
<p>
|
||||
<a href="https://ghostfol.io">Ghostfolio</a>, the web-based personal
|
||||
finance management software, is celebrating 500 stars on
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>. This
|
||||
is a major milestone for this open source project and a good time
|
||||
for another
|
||||
<a href="../en/blog/2022/01/ghostfolio-first-months-in-open-source"
|
||||
>recap</a
|
||||
>.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Growing Community</h2>
|
||||
<p>
|
||||
The Ghostfolio community is growing on various platforms and has
|
||||
recently passed 100 members on
|
||||
<a
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
>Slack</a
|
||||
>
|
||||
as well as 100 followers on
|
||||
<a href="https://twitter.com/ghostfolio_">Twitter</a>. If you have
|
||||
not joined yet, this is a good time to make sure you do not miss out
|
||||
on any future updates.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Message Queue: Asynchronous Processing</h2>
|
||||
<p>
|
||||
Overall
|
||||
<a href="https://status.ghostfol.io">stability and robustness</a>
|
||||
has increased significantly since the introduction of a
|
||||
<a href="https://github.com/OptimalBits/bull">message queue</a>. The
|
||||
workers of this robust queue system process jobs, namely gathering
|
||||
historical market data, asynchronously in the background to not
|
||||
bother the main service.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Ready for Web 3.0</h2>
|
||||
<p>
|
||||
The
|
||||
<a href="../en/blog/2022/07/ghostfolio-meets-internet-identity"
|
||||
>recent integration of Internet Identity</a
|
||||
>, a blockchain authentication system, makes Ghostfolio ready for
|
||||
Web3. This third iteration of the World Wide Web is the vision of a
|
||||
new and better Internet based on decentralized blockchains to give
|
||||
power back to the users. <i>Internet Identity</i> created by the
|
||||
<a href="https://dfinity.org">Dfinity Foundation</a> enables you to
|
||||
sign in securely and anonymously to Ghostfolio without an email
|
||||
address, username, or a password. All you need is your device with
|
||||
built-in biometric authentication.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Break-even Point</h2>
|
||||
<p>
|
||||
Despite the complicated
|
||||
<a [routerLink]="['/markets']">economic situation</a> at this time,
|
||||
the goal set at the beginning of the year to build a sustainable
|
||||
business and reach break-even with the SaaS offering (<a
|
||||
[routerLink]="['/markets']"
|
||||
>Ghostfolio Premium</a
|
||||
>) has been achieved. We will continue to leverage the revenue to
|
||||
further improve the fully managed cloud offering for our paying
|
||||
customers. A new goal we have set for ourselves is to become
|
||||
profitable.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Outlook</h2>
|
||||
<p>
|
||||
Besides all the positive accomplishments during the last months,
|
||||
there is still a lot of room for improvement. It would be great to
|
||||
onboard more contributors who are actively involved in software
|
||||
engineering to realize the full potential of open source software.
|
||||
If you are a web developer and interested in personal finance,
|
||||
please get in touch by email via
|
||||
<a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a> or on Twitter
|
||||
<a href="https://twitter.com/ghostfolio_">@ghostfolio_</a>. We are
|
||||
happy to discuss ideas.
|
||||
</p>
|
||||
<p>
|
||||
We would like to say thank you for all your feedback and support
|
||||
since the beginning of this project.
|
||||
</p>
|
||||
<p>
|
||||
Off to the next 500 stars!<br />
|
||||
Thomas from Ghostfolio
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<ul class="list-inline">
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Blockchain</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">BuildInPublic</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Cloud</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Community</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Finance</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Fintech</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Future</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Goal</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Internet Identity</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Investment</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Message Queue</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">OpenSaaS</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Open Source</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">OSS</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Personal Finance</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Planning</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Portfolio</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Portfolio Tracker</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Progress</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">SaaS</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Software</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">User Feedback</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Wealth</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Wealth Management</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Web3</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Web 3.0</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Worker</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { FiveHundredStarsOnGitHubRoutingModule } from './500-stars-on-github-page-routing.module';
|
||||
import { FiveHundredStarsOnGitHubPageComponent } from './500-stars-on-github-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FiveHundredStarsOnGitHubPageComponent],
|
||||
imports: [CommonModule, FiveHundredStarsOnGitHubRoutingModule, RouterModule],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class FiveHundredStarsOnGitHubPageModule {}
|
@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: BlogPageComponent,
|
||||
path: '',
|
||||
title: 'Blog'
|
||||
title: $localize`Blog`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -8,7 +8,31 @@
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex w-100"
|
||||
[routerLink]="['/en', 'blog', '2022', '07', 'ghostfolio-meets-internet-identity']"
|
||||
href="../en/blog/2022/08/500-stars-on-github"
|
||||
>
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 m-0 text-truncate">500 Stars on GitHub</div>
|
||||
<div class="d-flex text-muted">2022-08-18</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
></ion-icon>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex w-100"
|
||||
href="../en/blog/2022/07/ghostfolio-meets-internet-identity"
|
||||
>
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 m-0 text-truncate">
|
||||
@ -34,7 +58,7 @@
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex w-100"
|
||||
[routerLink]="['/en', 'blog', '2022', '07', 'how-do-i-get-my-finances-in-order']"
|
||||
href="../en/blog/2022/07/how-do-i-get-my-finances-in-order"
|
||||
>
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 m-0 text-truncate">
|
||||
@ -60,7 +84,7 @@
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex w-100"
|
||||
[routerLink]="['/en', 'blog', '2022', '01', 'ghostfolio-first-months-in-open-source']"
|
||||
href="'../en/blog/2022/01/ghostfolio-first-months-in-open-source"
|
||||
>
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 m-0 text-truncate">
|
||||
@ -86,7 +110,7 @@
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex w-100"
|
||||
[routerLink]="['/en', 'blog', '2021', '07', 'hello-ghostfolio']"
|
||||
href="../en/blog/2021/07/hello-ghostfolio"
|
||||
>
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 m-0 text-truncate">Hello Ghostfolio</div>
|
||||
@ -110,7 +134,7 @@
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex w-100"
|
||||
[routerLink]="['/de', 'blog', '2021', '07', 'hallo-ghostfolio']"
|
||||
href="../de/blog/2021/07/hallo-ghostfolio"
|
||||
>
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 m-0 text-truncate">Hallo Ghostfolio</div>
|
||||
|
@ -28,7 +28,7 @@ export class DemoPageComponent implements OnDestroy {
|
||||
|
||||
if (hasToken) {
|
||||
alert(
|
||||
'As you are already logged in, you cannot access the demo account.'
|
||||
$localize`As you are already logged in, you cannot access the demo account.`
|
||||
);
|
||||
} else {
|
||||
this.tokenStorageService.saveToken(this.info.demoAuthToken, true);
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: FaqPageComponent,
|
||||
path: '',
|
||||
title: 'FAQ'
|
||||
title: $localize`FAQ`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -35,8 +35,7 @@
|
||||
>Get Started</a
|
||||
>” button at the top of the page. You have multiple options to join
|
||||
Ghostfolio: Create an account with a security token, using
|
||||
<a
|
||||
[routerLink]="['/en', 'blog', '2022', '07', 'ghostfolio-meets-internet-identity']"
|
||||
<a href="../en/blog/2022/07/ghostfolio-meets-internet-identity"
|
||||
>Internet Identity</a
|
||||
>
|
||||
or <i>Google Sign</i>. We will guide you to set up your portfolio.
|
||||
@ -46,8 +45,7 @@
|
||||
<mat-card-title i18n>Can I use Ghostfolio anonymously?</mat-card-title>
|
||||
<mat-card-content i18n>
|
||||
Yes, the authentication systems (via security token or
|
||||
<a
|
||||
[routerLink]="['/en', 'blog', '2022', '07', 'ghostfolio-meets-internet-identity']"
|
||||
<a href="../en/blog/2022/07/ghostfolio-meets-internet-identity"
|
||||
>Internet Identity</a
|
||||
>) enable you to sign in securely and anonymously to Ghostfolio. There
|
||||
is no need for an email address, phone number, or a username.
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: FeaturesPageComponent,
|
||||
path: '',
|
||||
title: 'Features'
|
||||
title: $localize`Features`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -20,7 +20,7 @@ const routes: Routes = [
|
||||
],
|
||||
component: HomePageComponent,
|
||||
path: '',
|
||||
title: 'Overview'
|
||||
title: $localize`Overview`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
<img
|
||||
alt="Ghostfol.io Trailer"
|
||||
class="rounded video"
|
||||
src="./assets/images/video-preview.jpg"
|
||||
src="../assets/images/video-preview.jpg"
|
||||
style="max-width: 100%; width: 40rem"
|
||||
/>
|
||||
</a>
|
||||
@ -183,7 +183,7 @@
|
||||
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
||||
title="Get Ghostfolio on Google Play"
|
||||
>
|
||||
<img alt="Google Play Badge" src="assets/badge-en-google-play.png" />
|
||||
<img alt="Google Play Badge" src="../assets/badge-en-google-play.png" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: MarketsPageComponent,
|
||||
path: '',
|
||||
title: 'Markets'
|
||||
title: $localize`Markets`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: AllocationsPageComponent,
|
||||
path: '',
|
||||
title: 'Allocations'
|
||||
title: $localize`Allocations`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: AnalysisPageComponent,
|
||||
path: '',
|
||||
title: 'Analysis'
|
||||
title: $localize`Analysis`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: FirePageComponent,
|
||||
path: '',
|
||||
title: 'FIRE'
|
||||
title: $localize`FIRE`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: HoldingsPageComponent,
|
||||
path: '',
|
||||
title: 'Holdings'
|
||||
title: $localize`Holdings`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: TransactionsPageComponent,
|
||||
path: '',
|
||||
title: 'Activities'
|
||||
title: $localize`Activities`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -188,7 +188,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
||||
input.type = 'file';
|
||||
|
||||
input.onchange = (event) => {
|
||||
this.snackBar.open('⏳ Importing data...');
|
||||
this.snackBar.open('⏳' + $localize`Importing data...`);
|
||||
|
||||
// Getting the file reference
|
||||
const file = (event.target as HTMLInputElement).files[0];
|
||||
@ -334,7 +334,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
||||
private handleImportSuccess() {
|
||||
this.fetchActivities();
|
||||
|
||||
this.snackBar.open('✅ Import has been completed', undefined, {
|
||||
this.snackBar.open('✅' + $localize`Import has been completed`, undefined, {
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: PricingPageComponent,
|
||||
path: '',
|
||||
title: 'Pricing'
|
||||
title: $localize`Pricing`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: PublicPageComponent,
|
||||
path: ':id',
|
||||
title: 'Portfolio'
|
||||
title: $localize`Portfolio`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: RegisterPageComponent,
|
||||
path: '',
|
||||
title: 'Registration'
|
||||
title: $localize`Registration`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -36,15 +36,15 @@
|
||||
>
|
||||
<img
|
||||
class="mr-2"
|
||||
src="./assets/icons/internet-computer.svg"
|
||||
src="../assets/icons/internet-computer.svg"
|
||||
style="height: 0.75rem"
|
||||
/>
|
||||
<span i18n>Continue with Internet Identity</span>
|
||||
</button>
|
||||
<a class="d-block" href="/api/v1/auth/google" mat-stroked-button
|
||||
<a class="d-block" href="../api/v1/auth/google" mat-stroked-button
|
||||
><img
|
||||
class="mr-2"
|
||||
src="./assets/icons/google.svg"
|
||||
src="../assets/icons/google.svg"
|
||||
style="height: 1rem"
|
||||
/><span i18n>Continue with Google</span></a
|
||||
>
|
||||
|
@ -9,7 +9,7 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
component: ResourcesPageComponent,
|
||||
path: '',
|
||||
title: 'Resources'
|
||||
title: $localize`Resources`
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -29,8 +29,7 @@
|
||||
easier and faster in this guide.
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
[routerLink]="['/en', 'blog', '2022', '07', 'how-do-i-get-my-finances-in-order']"
|
||||
<a href="../en/blog/2022/07/how-do-i-get-my-finances-in-order"
|
||||
>How do I get my finances in order? →</a
|
||||
>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import { WebauthnPageComponent } from '@ghostfolio/client/pages/webauthn/webauthn-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ component: WebauthnPageComponent, path: '', title: 'Login' }
|
||||
{ component: WebauthnPageComponent, path: '', title: $localize`Login` }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -16,7 +16,7 @@ const routes: Routes = [
|
||||
],
|
||||
component: ZenPageComponent,
|
||||
path: '',
|
||||
title: 'Overview'
|
||||
title: $localize`Overview`
|
||||
}
|
||||
];
|
||||
|
||||
|
BIN
apps/client/src/assets/images/blog/500-stars-on-github.jpg
Normal file
BIN
apps/client/src/assets/images/blog/500-stars-on-github.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 197 KiB |
@ -1,6 +1,6 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /about/privacy-policy
|
||||
Disallow: /p/*
|
||||
Disallow: /en/about/privacy-policy
|
||||
Disallow: /en/p/*
|
||||
|
||||
Sitemap: https://ghostfol.io/sitemap.xml
|
||||
|
@ -6,12 +6,12 @@
|
||||
"icons": [
|
||||
{
|
||||
"sizes": "192x192",
|
||||
"src": "/assets/android-chrome-192x192.png",
|
||||
"src": "/en/assets/android-chrome-192x192.png",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"sizes": "512x512",
|
||||
"src": "/assets/android-chrome-512x512.png",
|
||||
"src": "/en/assets/android-chrome-512x512.png",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
|
@ -6,66 +6,70 @@
|
||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>https://ghostfol.io</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/about</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/about/changelog</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/blog</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/demo</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/about</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/about/changelog</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/faq</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/features</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/demo</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/markets</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/faq</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/pricing</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/features</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/register</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/markets</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/resources</loc>
|
||||
<lastmod>2022-07-29T00:00:00+00:00</lastmod>
|
||||
<loc>https://ghostfol.io/en/pricing</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/register</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/resources</loc>
|
||||
<lastmod>2022-08-18T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
|
@ -19,10 +19,7 @@
|
||||
name="twitter:description"
|
||||
content="Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://www.ghostfol.io/assets/cover.png"
|
||||
/>
|
||||
<meta name="twitter:image" content="https://ghostfol.io/assets/cover.png" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Ghostfolio – Open Source Wealth Management Software"
|
||||
@ -37,12 +34,9 @@
|
||||
content="Ghostfolio – Open Source Wealth Management Software"
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://www.ghostfol.io" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://www.ghostfol.io/assets/cover.png"
|
||||
/>
|
||||
<meta property="og:updated_time" content="2022-05-28T00:00:00+00:00" />
|
||||
<meta property="og:url" content="https://ghostfol.io" />
|
||||
<meta property="og:image" content="https://ghostfol.io/assets/cover.png" />
|
||||
<meta property="og:updated_time" content="2022-08-18T00:00:00+00:00" />
|
||||
<meta
|
||||
property="og:site_name"
|
||||
content="Ghostfolio – Open Source Wealth Management Software"
|
||||
@ -51,26 +45,26 @@
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/assets/apple-touch-icon.png"
|
||||
href="../assets/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/assets/favicon-32x32.png"
|
||||
href="../assets/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/assets/favicon-16x16.png"
|
||||
href="../assets/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/assets/site.webmanifest" />
|
||||
<link rel="manifest" href="../assets/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<gf-root></gf-root>
|
||||
|
||||
<script type="module" src="ionicons/ionicons.esm.js"></script>
|
||||
<script type="module" src="../ionicons/ionicons.esm.js"></script>
|
||||
<script nomodule="" src="ionicons.js"></script>
|
||||
|
||||
<noscript
|
||||
|
2946
apps/client/src/locales/messages.de.xlf
Normal file
2946
apps/client/src/locales/messages.de.xlf
Normal file
File diff suppressed because it is too large
Load Diff
2614
apps/client/src/locales/messages.xlf
Normal file
2614
apps/client/src/locales/messages.xlf
Normal file
File diff suppressed because it is too large
Load Diff
2
apps/client/src/styles/bootstrap.scss
vendored
2
apps/client/src/styles/bootstrap.scss
vendored
@ -11,7 +11,7 @@
|
||||
@import '~bootstrap/scss/root';
|
||||
@import '~bootstrap/scss/reboot';
|
||||
@import '~bootstrap/scss/type';
|
||||
// @import '~bootstrap/scss/images';
|
||||
@import '~bootstrap/scss/images';
|
||||
// @import '~bootstrap/scss/code';
|
||||
@import '~bootstrap/scss/grid';
|
||||
// @import '~bootstrap/scss/tables';
|
||||
|
@ -2,16 +2,6 @@ import { DataSource } from '@prisma/client';
|
||||
import { JobOptions, JobStatus } from 'bull';
|
||||
import ms from 'ms';
|
||||
|
||||
import { ToggleOption } from './types';
|
||||
|
||||
export const defaultDateRangeOptions: ToggleOption[] = [
|
||||
{ label: 'Today', value: '1d' },
|
||||
{ label: 'YTD', value: 'ytd' },
|
||||
{ label: '1Y', value: '1y' },
|
||||
{ label: '5Y', value: '5y' },
|
||||
{ label: 'Max', value: 'max' }
|
||||
];
|
||||
|
||||
export const DEMO_USER_ID = '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f';
|
||||
|
||||
export const ghostfolioScraperApiSymbolPrefix = '_GF_';
|
||||
@ -49,6 +39,7 @@ export const DATA_GATHERING_QUEUE_PRIORITY_LOW = Number.MAX_SAFE_INTEGER;
|
||||
export const DATA_GATHERING_QUEUE_PRIORITY_HIGH = 1;
|
||||
|
||||
export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy';
|
||||
export const DEFAULT_LANGUAGE_CODE = 'en';
|
||||
|
||||
export const GATHER_ASSET_PROFILE_PROCESS = 'GATHER_ASSET_PROFILE';
|
||||
export const GATHER_ASSET_PROFILE_PROCESS_OPTIONS: JobOptions = {
|
||||
|
@ -132,7 +132,9 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
public onDeleteActivity(aId: string) {
|
||||
const confirmation = confirm('Do you really want to delete this activity?');
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to delete this activity?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
this.activityDeleted.emit(aId);
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "1.178.0",
|
||||
"version": "1.180.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@ -13,7 +13,7 @@
|
||||
"affected:lint": "nx affected:lint",
|
||||
"affected:test": "nx affected:test",
|
||||
"angular": "node --max_old_space_size=32768 ./node_modules/@angular/cli/bin/ng",
|
||||
"build:all": "nx run api:build:production && nx run client:build:production && yarn replace-placeholders-in-build",
|
||||
"build:all": "nx run api:build:production && nx run client:build:production --localize && yarn replace-placeholders-in-build",
|
||||
"build:dev": "nx run api:build && nx run client:build && yarn replace-placeholders-in-build",
|
||||
"build:storybook": "nx run ui:build-storybook",
|
||||
"clean": "rimraf dist",
|
||||
@ -29,6 +29,7 @@
|
||||
"database:validate": "prisma validate",
|
||||
"dep-graph": "nx dep-graph",
|
||||
"e2e": "ng e2e",
|
||||
"extract-locales": "ng extract-i18n client --output-path ./apps/client/src/locales",
|
||||
"format": "nx format:write",
|
||||
"format:check": "nx format:check",
|
||||
"format:write": "nx format:write",
|
||||
@ -40,7 +41,7 @@
|
||||
"postinstall": "prisma generate && ngcc --properties es2020 browser module main",
|
||||
"replace-placeholders-in-build": "node ./replace.build.js",
|
||||
"start": "node dist/apps/api/main",
|
||||
"start:client": "ng serve client --hmr -o",
|
||||
"start:client": "ng serve client --configuration=development-en --hmr -o",
|
||||
"start:prod": "node apps/api/main",
|
||||
"start:server": "nx serve api --watch",
|
||||
"start:storybook": "nx run ui:storybook",
|
||||
@ -110,6 +111,7 @@
|
||||
"ionicons": "5.5.1",
|
||||
"lodash": "4.17.21",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"ng-extract-i18n-merge": "2.1.2",
|
||||
"ngx-device-detector": "3.0.0",
|
||||
"ngx-markdown": "14.0.1",
|
||||
"ngx-skeleton-loader": "5.0.0",
|
||||
|
121
yarn.lock
121
yarn.lock
@ -18,6 +18,14 @@
|
||||
"@angular-devkit/core" "14.1.0"
|
||||
rxjs "6.6.7"
|
||||
|
||||
"@angular-devkit/architect@^0.1301.0":
|
||||
version "0.1301.4"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.1301.4.tgz#2fc51bcae0dcb581c8be401e2fde7bbd10b43076"
|
||||
integrity sha512-p6G8CEMnE+gYwxRyEttj3QGsuNJ3Kusi7iwBIzWyf2RpJSdGzXdwUEiRGg6iS0YHFr06/ZFfAWfnM2DQvNm4TA==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "13.1.4"
|
||||
rxjs "6.6.7"
|
||||
|
||||
"@angular-devkit/build-angular@14.1.0":
|
||||
version "14.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-14.1.0.tgz#f2aaaa4d28c0f39fdcfee6c0241e1bedb576b2d4"
|
||||
@ -96,6 +104,18 @@
|
||||
"@angular-devkit/architect" "0.1401.0"
|
||||
rxjs "6.6.7"
|
||||
|
||||
"@angular-devkit/core@13.1.4":
|
||||
version "13.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-13.1.4.tgz#b5b6ddd674ae351f83beff2e4a0d702096bdfd47"
|
||||
integrity sha512-225Gjy4iVxh5Jo9njJnaG75M/Dt95UW+dEPCGWKV5E/++7UUlXlo9sNWq8x2vJm2nhtsPkpnXNOt4pW1mIDwqQ==
|
||||
dependencies:
|
||||
ajv "8.8.2"
|
||||
ajv-formats "2.1.1"
|
||||
fast-json-stable-stringify "2.1.0"
|
||||
magic-string "0.25.7"
|
||||
rxjs "6.6.7"
|
||||
source-map "0.7.3"
|
||||
|
||||
"@angular-devkit/core@14.0.5":
|
||||
version "14.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-14.0.5.tgz#19f5940b53aeb0ce56479c44670d3bc3b2df92b1"
|
||||
@ -118,6 +138,17 @@
|
||||
rxjs "6.6.7"
|
||||
source-map "0.7.4"
|
||||
|
||||
"@angular-devkit/core@14.1.2", "@angular-devkit/core@^13.0.0 || ^14.0.0":
|
||||
version "14.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-14.1.2.tgz#f4c287295065b3845e85a84e29f12629b4a52cf9"
|
||||
integrity sha512-fIfymD1erjoj1eVh7pa/dvOtUhSd7sEOGuWEJ81HJqdzwZbPWweRu3Nh/9kj/ttUy8xawWfdJHLwyG2KnRu0DA==
|
||||
dependencies:
|
||||
ajv "8.11.0"
|
||||
ajv-formats "2.1.1"
|
||||
jsonc-parser "3.1.0"
|
||||
rxjs "6.6.7"
|
||||
source-map "0.7.4"
|
||||
|
||||
"@angular-devkit/schematics@14.0.5":
|
||||
version "14.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-14.0.5.tgz#01777d2ad473d35bdfdbbb751521c43421ad9772"
|
||||
@ -140,6 +171,17 @@
|
||||
ora "5.4.1"
|
||||
rxjs "6.6.7"
|
||||
|
||||
"@angular-devkit/schematics@14.1.2", "@angular-devkit/schematics@^13.0.0 || ^14.0.0":
|
||||
version "14.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-14.1.2.tgz#6fb81aaece4ad1a24f3dd7b213e2f7000b41adbf"
|
||||
integrity sha512-vC9UA3heXbq9CAkwMXHJfIu0G7Ty2oTJ5PtrrFQpig1PrFnRfr4sg+qRS1CLsEAJYQNo14xV55OQkKEPTij/Gg==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "14.1.2"
|
||||
jsonc-parser "3.1.0"
|
||||
magic-string "0.26.2"
|
||||
ora "5.4.1"
|
||||
rxjs "6.6.7"
|
||||
|
||||
"@angular-eslint/bundled-angular-compiler@14.0.2":
|
||||
version "14.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-14.0.2.tgz#16aed25430b96fac50d069b2e44dae89fa4b12ad"
|
||||
@ -3705,6 +3747,15 @@
|
||||
"@angular-devkit/schematics" "14.1.0"
|
||||
jsonc-parser "3.1.0"
|
||||
|
||||
"@schematics/angular@^13.0.0 || ^14.0.0":
|
||||
version "14.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-14.1.2.tgz#edc828c3bf3e5eb9c55d11e560d3fd680819021c"
|
||||
integrity sha512-vttvYpffkG/cy9aUCXLW+Dc0msUNbyoFXTQRHN/MYX5uNVvXaRCEzWVE3tp87Dt5XlGo2r9e21gNAoY1TyXj3Q==
|
||||
dependencies:
|
||||
"@angular-devkit/core" "14.1.2"
|
||||
"@angular-devkit/schematics" "14.1.2"
|
||||
jsonc-parser "3.1.0"
|
||||
|
||||
"@simplewebauthn/browser@5.2.1":
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@simplewebauthn/browser/-/browser-5.2.1.tgz#569252a9f235a99aae90c4d1cc6c441f42637b8e"
|
||||
@ -5864,6 +5915,16 @@ ajv@8.11.0, ajv@^8.0.0, ajv@^8.8.0:
|
||||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@8.8.2:
|
||||
version "8.8.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb"
|
||||
integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.9.1:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
@ -7679,11 +7740,16 @@ commander@^6.2.1:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
|
||||
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
|
||||
|
||||
commander@^8.0.0, commander@^8.3.0:
|
||||
commander@^8.0.0, commander@^8.3.0, commander@~8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
|
||||
commander@~7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
|
||||
integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
|
||||
|
||||
common-path-prefix@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0"
|
||||
@ -10319,7 +10385,7 @@ fast-glob@^3.0.3, fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9:
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
|
||||
fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
@ -13113,6 +13179,11 @@ jest@27.5.1:
|
||||
import-local "^3.0.2"
|
||||
jest-cli "^27.5.1"
|
||||
|
||||
js-levenshtein@~1.1.6:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
|
||||
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
|
||||
|
||||
js-sha256@0.9.0, js-sha256@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
|
||||
@ -13812,6 +13883,13 @@ luxon@^1.23.x, luxon@^1.28.0:
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
|
||||
integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==
|
||||
|
||||
magic-string@0.25.7:
|
||||
version "0.25.7"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.4"
|
||||
|
||||
magic-string@0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.1.tgz#ba9b651354fa9512474199acecf9c6dbe93f97fd"
|
||||
@ -14495,6 +14573,19 @@ nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5"
|
||||
integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==
|
||||
|
||||
ng-extract-i18n-merge@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.1.2.tgz#1a86beccc85cf2fbeb7538009feeb8a6787d602a"
|
||||
integrity sha512-dRo/oO4m3tr26uE4+DHJh0iTqSV3qVl+WGTSHGKKyAM628dP3hZunhBTs7fCpLs57aEex84cJax8X+Y8wmBXTQ==
|
||||
dependencies:
|
||||
"@angular-devkit/architect" "^0.1301.0"
|
||||
"@angular-devkit/core" "^13.0.0 || ^14.0.0"
|
||||
"@angular-devkit/schematics" "^13.0.0 || ^14.0.0"
|
||||
"@schematics/angular" "^13.0.0 || ^14.0.0"
|
||||
xliff-simple-merge "~0.12.4"
|
||||
xml_normalize "~0.8.4"
|
||||
xmldoc "~1.1.2"
|
||||
|
||||
ngx-device-detector@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-device-detector/-/ngx-device-detector-3.0.0.tgz#9c5b1db66e03837d5de0e93fe4a1de93948c9c81"
|
||||
@ -17901,7 +17992,7 @@ source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
|
||||
|
||||
sourcemap-codec@^1.4.8:
|
||||
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
@ -19873,16 +19964,40 @@ x-default-browser@^0.4.0:
|
||||
optionalDependencies:
|
||||
default-browser-id "^1.0.4"
|
||||
|
||||
xliff-simple-merge@~0.12.4:
|
||||
version "0.12.4"
|
||||
resolved "https://registry.yarnpkg.com/xliff-simple-merge/-/xliff-simple-merge-0.12.4.tgz#d3e712c71bec4344558b11a5e2578915cee943ec"
|
||||
integrity sha512-Rk88D62XUoMgyBLEILpgOx3ARnxONLwH/xz+/c1HsRHYPHDnuZhYORkwrBCXsE942esG+J+OUCCOiydXJxY4Ug==
|
||||
dependencies:
|
||||
commander "~8.3.0"
|
||||
js-levenshtein "~1.1.6"
|
||||
xmldoc "~1.1.2"
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
|
||||
|
||||
xml_normalize@~0.8.4:
|
||||
version "0.8.4"
|
||||
resolved "https://registry.yarnpkg.com/xml_normalize/-/xml_normalize-0.8.4.tgz#589c88a0c3f0fe7e89c94ae14b82de02ebfc1f2c"
|
||||
integrity sha512-I+PiD+W/fS7YOWb4THhQfvkzd1mCuTT3KoRt4PTxiWYbH1RY74w5ewcIas8ja9APC8Ho2izg8VcNnTTN35qM9A==
|
||||
dependencies:
|
||||
commander "~7.1.0"
|
||||
xmldoc "~1.1.2"
|
||||
|
||||
xmlchars@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xmldoc@~1.1.2:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.1.4.tgz#ea4e26dca76b1d218a2f777018bce404ba374a86"
|
||||
integrity sha512-rQshsBGR5s7pUNENTEncpI2LTCuzicri0DyE4SCV5XmS0q81JS8j1iPijP0Q5c4WLGbKh3W92hlOwY6N9ssW1w==
|
||||
dependencies:
|
||||
sax "^1.2.4"
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
Reference in New Issue
Block a user