Feature/migrate client to control flow (#3475)
* Migrate to control flow * Update changelog
This commit is contained in:
parent
88c420ca5e
commit
b725e6e2ec
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Migrated the `@ghostfolio/client` components to control flow
|
||||
- Improved the language localization for German (`de`)
|
||||
- Upgraded `angular` from version `17.3.10` to `18.0.2`
|
||||
- Upgraded `Nx` from version `19.0.5` to `19.2.2`
|
||||
|
@ -1,33 +1,31 @@
|
||||
<header>
|
||||
<div
|
||||
*ngIf="canCreateAccount || user?.systemMessage"
|
||||
class="info-message-container"
|
||||
>
|
||||
<div class="info-message-inner-container position-fixed w-100">
|
||||
<div class="align-items-center d-flex h-100 justify-content-center">
|
||||
<a
|
||||
*ngIf="canCreateAccount"
|
||||
class="text-center"
|
||||
[routerLink]="routerLinkRegister"
|
||||
>
|
||||
<div
|
||||
class="cursor-pointer d-inline-block info-message"
|
||||
(click)="onCreateAccount()"
|
||||
>
|
||||
<span i18n>You are using the Live Demo.</span>
|
||||
<span class="a ml-2" i18n>Create Account</span>
|
||||
</div></a
|
||||
>
|
||||
<div
|
||||
*ngIf="!canCreateAccount && user?.systemMessage"
|
||||
class="cursor-pointer d-inline-block info-message text-truncate"
|
||||
(click)="onClickSystemMessage()"
|
||||
>
|
||||
{{ user.systemMessage.message }}
|
||||
@if (canCreateAccount || user?.systemMessage) {
|
||||
<div class="info-message-container">
|
||||
<div class="info-message-inner-container position-fixed w-100">
|
||||
<div class="align-items-center d-flex h-100 justify-content-center">
|
||||
@if (canCreateAccount) {
|
||||
<a class="text-center" [routerLink]="routerLinkRegister">
|
||||
<div
|
||||
class="cursor-pointer d-inline-block info-message"
|
||||
(click)="onCreateAccount()"
|
||||
>
|
||||
<span i18n>You are using the Live Demo.</span>
|
||||
<span class="a ml-2" i18n>Create Account</span>
|
||||
</div></a
|
||||
>
|
||||
}
|
||||
@if (!canCreateAccount && user?.systemMessage) {
|
||||
<div
|
||||
class="cursor-pointer d-inline-block info-message text-truncate"
|
||||
(click)="onClickSystemMessage()"
|
||||
>
|
||||
{{ user.systemMessage.message }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<gf-header
|
||||
class="position-fixed w-100"
|
||||
@ -45,144 +43,159 @@
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<footer *ngIf="showFooter" class="d-flex justify-content-center py-4 w-100">
|
||||
<div class="container">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-sm">
|
||||
<a [routerLink]="['/']"><gf-logo /></a>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="h6 mt-2" i18n>Personal Finance</div>
|
||||
<ul class="list-unstyled">
|
||||
<li *ngIf="hasPermissionToAccessFearAndGreedIndex">
|
||||
<a i18n [routerLink]="routerLinkMarkets">Markets</a>
|
||||
</li>
|
||||
<li><a i18n [routerLink]="routerLinkResources">Resources</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="h6 mt-2">Ghostfolio</div>
|
||||
<ul class="list-unstyled">
|
||||
<li><a i18n [routerLink]="routerLinkAbout">About</a></li>
|
||||
<li *ngIf="hasPermissionForSubscription">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a i18n [routerLink]="routerLinkAboutChangelog">Changelog</a>
|
||||
</li>
|
||||
<li><a i18n [routerLink]="routerLinkFeatures">Features</a></li>
|
||||
<li *ngIf="hasPermissionForSubscription">
|
||||
<a i18n [routerLink]="routerLinkFaq"
|
||||
>Frequently Asked Questions (FAQ)</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a i18n [routerLink]="routerLinkAboutLicense">License</a>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionForStatistics">
|
||||
<a [routerLink]="['/open']">Open Startup</a>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionForSubscription">
|
||||
<a i18n [routerLink]="routerLinkPricing">Pricing</a>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionForSubscription">
|
||||
<a i18n [routerLink]="routerLinkAboutPrivacyPolicy"
|
||||
>Privacy Policy</a
|
||||
>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionForSubscription">
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://status.ghostfol.io"
|
||||
target="_blank"
|
||||
title="Ghostfolio Status"
|
||||
>Status<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="h6 mt-2" i18n>Community</div>
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
target="_blank"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
target="_blank"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
target="_blank"
|
||||
title="Follow Ghostfolio on X (formerly Twitter)"
|
||||
>X (formerly Twitter)<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
<li> </li>
|
||||
<li>
|
||||
<a href="../de" title="Ghostfolio in Deutsch">Deutsch</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../en" title="Ghostfolio in English">English</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../es" title="Ghostfolio in Español">Español</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../fr" title="Ghostfolio en Français">Français</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../it" title="Ghostfolio in Italiano">Italiano</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../nl" title="Ghostfolio in Nederlands">Nederlands</a>
|
||||
</li>
|
||||
<!--
|
||||
@if (showFooter) {
|
||||
<footer class="d-flex justify-content-center py-4 w-100">
|
||||
<div class="container">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-sm">
|
||||
<a [routerLink]="['/']"><gf-logo /></a>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="h6 mt-2" i18n>Personal Finance</div>
|
||||
<ul class="list-unstyled">
|
||||
@if (hasPermissionToAccessFearAndGreedIndex) {
|
||||
<li>
|
||||
<a i18n [routerLink]="routerLinkMarkets">Markets</a>
|
||||
</li>
|
||||
}
|
||||
<li><a i18n [routerLink]="routerLinkResources">Resources</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="h6 mt-2">Ghostfolio</div>
|
||||
<ul class="list-unstyled">
|
||||
<li><a i18n [routerLink]="routerLinkAbout">About</a></li>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<li>
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="../pl" title="Ghostfolio in Polski">Polski</a>
|
||||
<a i18n [routerLink]="routerLinkAboutChangelog">Changelog</a>
|
||||
</li>
|
||||
-->
|
||||
<li>
|
||||
<a href="../pt" title="Ghostfolio in Português">Português</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../tr" title="Ghostfolio in Türkçe">Türkçe</a>
|
||||
</li>
|
||||
<!--
|
||||
<li><a i18n [routerLink]="routerLinkFeatures">Features</a></li>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<li>
|
||||
<a i18n [routerLink]="routerLinkFaq"
|
||||
>Frequently Asked Questions (FAQ)</a
|
||||
>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="../zh" title="Ghostfolio in Chinese">Chinese</a>
|
||||
<a i18n [routerLink]="routerLinkAboutLicense">License</a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
@if (hasPermissionForStatistics) {
|
||||
<li>
|
||||
<a [routerLink]="['/open']">Open Startup</a>
|
||||
</li>
|
||||
}
|
||||
@if (hasPermissionForSubscription) {
|
||||
<li>
|
||||
<a i18n [routerLink]="routerLinkPricing">Pricing</a>
|
||||
</li>
|
||||
}
|
||||
@if (hasPermissionForSubscription) {
|
||||
<li>
|
||||
<a i18n [routerLink]="routerLinkAboutPrivacyPolicy"
|
||||
>Privacy Policy</a
|
||||
>
|
||||
</li>
|
||||
}
|
||||
@if (hasPermissionForSubscription) {
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://status.ghostfol.io"
|
||||
target="_blank"
|
||||
title="Ghostfolio Status"
|
||||
>Status<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="h6 mt-2" i18n>Community</div>
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
target="_blank"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>GitHub<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
target="_blank"
|
||||
title="Join the Ghostfolio Slack community"
|
||||
>Slack<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="align-items-baseline d-flex"
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
target="_blank"
|
||||
title="Follow Ghostfolio on X (formerly Twitter)"
|
||||
>X (formerly Twitter)<ion-icon class="ml-1" name="open-outline"
|
||||
/></a>
|
||||
</li>
|
||||
<li> </li>
|
||||
<li>
|
||||
<a href="../de" title="Ghostfolio in Deutsch">Deutsch</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../en" title="Ghostfolio in English">English</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../es" title="Ghostfolio in Español">Español</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../fr" title="Ghostfolio en Français">Français</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../it" title="Ghostfolio in Italiano">Italiano</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../nl" title="Ghostfolio in Nederlands">Nederlands</a>
|
||||
</li>
|
||||
<!--
|
||||
<li>
|
||||
<a href="../pl" title="Ghostfolio in Polski">Polski</a>
|
||||
</li>
|
||||
-->
|
||||
<li>
|
||||
<a href="../pt" title="Ghostfolio in Português">Português</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../tr" title="Ghostfolio in Türkçe">Türkçe</a>
|
||||
</li>
|
||||
<!--
|
||||
<li>
|
||||
<a href="../zh" title="Ghostfolio in Chinese">Chinese</a>
|
||||
</li>
|
||||
-->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center">
|
||||
<div class="col">
|
||||
© 2021 - {{ currentYear }}
|
||||
<a href="https://ghostfol.io">Ghostfolio</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row text-center text-muted">
|
||||
<div class="col">
|
||||
<small i18n
|
||||
>The risk of loss in trading can be substantial. It is not advisable
|
||||
to invest money you may need in the short term.</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col">
|
||||
© 2021 - {{ currentYear }} <a href="https://ghostfol.io">Ghostfolio</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row text-center text-muted">
|
||||
<div class="col">
|
||||
<small i18n
|
||||
>The risk of loss in trading can be substantial. It is not advisable
|
||||
to invest money you may need in the short term.</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</footer>
|
||||
}
|
||||
|
@ -32,17 +32,16 @@
|
||||
<ng-container matColumnDef="details">
|
||||
<th *matHeaderCellDef class="px-1" i18n mat-header-cell>Details</th>
|
||||
<td *matCellDef="let element" class="px-1 text-nowrap" mat-cell>
|
||||
<div
|
||||
*ngIf="element.type === 'PUBLIC'"
|
||||
class="align-items-center d-flex"
|
||||
>
|
||||
<ion-icon class="mr-1" name="link-outline" />
|
||||
<a
|
||||
href="{{ baseUrl }}/{{ defaultLanguageCode }}/p/{{ element.id }}"
|
||||
target="_blank"
|
||||
>{{ baseUrl }}/{{ defaultLanguageCode }}/p/{{ element.id }}</a
|
||||
>
|
||||
</div>
|
||||
@if (element.type === 'PUBLIC') {
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon class="mr-1" name="link-outline" />
|
||||
<a
|
||||
href="{{ baseUrl }}/{{ defaultLanguageCode }}/p/{{ element.id }}"
|
||||
target="_blank"
|
||||
>{{ baseUrl }}/{{ defaultLanguageCode }}/p/{{ element.id }}</a
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -1,14 +1,16 @@
|
||||
<div *ngIf="showActions" class="d-flex justify-content-end">
|
||||
<button
|
||||
class="align-items-center d-flex"
|
||||
mat-stroked-button
|
||||
[disabled]="dataSource?.data.length < 2"
|
||||
(click)="onTransferBalance()"
|
||||
>
|
||||
<ion-icon class="mr-2" name="arrow-redo-outline" />
|
||||
<ng-container i18n>Transfer Cash Balance</ng-container>...
|
||||
</button>
|
||||
</div>
|
||||
@if (showActions) {
|
||||
<div class="d-flex justify-content-end">
|
||||
<button
|
||||
class="align-items-center d-flex"
|
||||
mat-stroked-button
|
||||
[disabled]="dataSource?.data.length < 2"
|
||||
(click)="onTransferBalance()"
|
||||
>
|
||||
<ion-icon class="mr-2" name="arrow-redo-outline" />
|
||||
<ng-container i18n>Transfer Cash Balance</ng-container>...
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="gf-table w-100" mat-table matSort [dataSource]="dataSource">
|
||||
@ -24,7 +26,9 @@
|
||||
mat-cell
|
||||
>
|
||||
<div class="d-flex justify-content-center">
|
||||
<ion-icon *ngIf="element.isExcluded" name="eye-off-outline" />
|
||||
@if (element.isExcluded) {
|
||||
<ion-icon name="eye-off-outline" />
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
@ -39,12 +43,13 @@
|
||||
<ng-container i18n>Name</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
<gf-asset-profile-icon
|
||||
*ngIf="element.Platform?.url"
|
||||
class="d-inline d-sm-none mr-1"
|
||||
[tooltip]="element.Platform?.name"
|
||||
[url]="element.Platform?.url"
|
||||
/>
|
||||
@if (element.Platform?.url) {
|
||||
<gf-asset-profile-icon
|
||||
class="d-inline d-sm-none mr-1"
|
||||
[tooltip]="element.Platform?.name"
|
||||
[url]="element.Platform?.url"
|
||||
/>
|
||||
}
|
||||
<span>{{ element.name }}</span>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>Total</td>
|
||||
@ -86,12 +91,13 @@
|
||||
mat-cell
|
||||
>
|
||||
<div class="d-flex">
|
||||
<gf-asset-profile-icon
|
||||
*ngIf="element.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="element.Platform?.name"
|
||||
[url]="element.Platform?.url"
|
||||
/>
|
||||
@if (element.Platform?.url) {
|
||||
<gf-asset-profile-icon
|
||||
class="mr-1"
|
||||
[tooltip]="element.Platform?.name"
|
||||
[url]="element.Platform?.url"
|
||||
/>
|
||||
}
|
||||
<span>{{ element.Platform?.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
@ -236,15 +242,16 @@
|
||||
class="d-none d-lg-table-cell px-1"
|
||||
mat-cell
|
||||
>
|
||||
<button
|
||||
*ngIf="element.comment"
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
title="Note"
|
||||
(click)="onOpenComment(element.comment); $event.stopPropagation()"
|
||||
>
|
||||
<ion-icon name="document-text-outline" />
|
||||
</button>
|
||||
@if (element.comment) {
|
||||
<button
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
title="Note"
|
||||
(click)="onOpenComment(element.comment); $event.stopPropagation()"
|
||||
>
|
||||
<ion-icon name="document-text-outline" />
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
*matFooterCellDef
|
||||
@ -303,12 +310,13 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="isLoading"
|
||||
animation="pulse"
|
||||
class="px-4 py-3"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (isLoading) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="px-4 py-3"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
|
@ -5,11 +5,14 @@
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-select formControlName="status">
|
||||
<mat-option />
|
||||
<mat-option
|
||||
*ngFor="let statusFilterOption of statusFilterOptions"
|
||||
[value]="statusFilterOption"
|
||||
>{{ statusFilterOption }}</mat-option
|
||||
>
|
||||
@for (
|
||||
statusFilterOption of statusFilterOptions;
|
||||
track statusFilterOption
|
||||
) {
|
||||
<mat-option [value]="statusFilterOption">{{
|
||||
statusFilterOption
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
@ -28,15 +31,11 @@
|
||||
<ng-container i18n>Type</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 py-2" mat-cell>
|
||||
<ng-container *ngIf="element.name === 'GATHER_ASSET_PROFILE'" i18n>
|
||||
Asset Profile
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="element.name === 'GATHER_HISTORICAL_MARKET_DATA'"
|
||||
i18n
|
||||
>
|
||||
Historical Market Data
|
||||
</ng-container>
|
||||
@if (element.name === 'GATHER_ASSET_PROFILE') {
|
||||
<ng-container i18n>Asset Profile</ng-container>
|
||||
} @else if (element.name === 'GATHER_HISTORICAL_MARKET_DATA') {
|
||||
<ng-container i18n>Historical Market Data</ng-container>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@ -109,37 +108,29 @@
|
||||
<ng-container i18n>Status</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 py-2" mat-cell>
|
||||
<ion-icon
|
||||
*ngIf="element.state === 'active'"
|
||||
class="h6 mb-0"
|
||||
name="play-outline"
|
||||
/>
|
||||
<ion-icon
|
||||
*ngIf="element.state === 'completed'"
|
||||
class="h6 mb-0 text-success"
|
||||
name="checkmark-circle-outline"
|
||||
/>
|
||||
<ion-icon
|
||||
*ngIf="element.state === 'delayed'"
|
||||
class="h6 mb-0"
|
||||
name="time-outline"
|
||||
[ngClass]="{ 'text-danger': element.stacktrace?.length > 0 }"
|
||||
/>
|
||||
<ion-icon
|
||||
*ngIf="element.state === 'failed'"
|
||||
class="h6 mb-0 text-danger"
|
||||
name="alert-circle-outline"
|
||||
/>
|
||||
<ion-icon
|
||||
*ngIf="element.state === 'paused'"
|
||||
class="h6 mb-0"
|
||||
name="pause-outline"
|
||||
/>
|
||||
<ion-icon
|
||||
*ngIf="element.state === 'waiting'"
|
||||
class="h6 mb-0"
|
||||
name="cafe-outline"
|
||||
/>
|
||||
@if (element.state === 'active') {
|
||||
<ion-icon class="h6 mb-0" name="play-outline" />
|
||||
} @else if (element.state === 'completed') {
|
||||
<ion-icon
|
||||
class="h6 mb-0 text-success"
|
||||
name="checkmark-circle-outline"
|
||||
/>
|
||||
} @else if (element.state === 'delayed') {
|
||||
<ion-icon
|
||||
class="h6 mb-0"
|
||||
name="time-outline"
|
||||
[ngClass]="{ 'text-danger': element.stacktrace?.length > 0 }"
|
||||
/>
|
||||
} @else if (element.state === 'failed') {
|
||||
<ion-icon
|
||||
class="h6 mb-0 text-danger"
|
||||
name="alert-circle-outline"
|
||||
/>
|
||||
} @else if (element.state === 'paused') {
|
||||
<ion-icon class="h6 mb-0" name="pause-outline" />
|
||||
} @else if (element.state === 'waiting') {
|
||||
<ion-icon class="h6 mb-0" name="cafe-outline" />
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -9,35 +9,38 @@
|
||||
[showYAxis]="true"
|
||||
[symbol]="symbol"
|
||||
/>
|
||||
<div *ngFor="let itemByMonth of marketDataByMonth | keyvalue" class="d-flex">
|
||||
<div class="date px-1 text-nowrap">{{ itemByMonth.key }}</div>
|
||||
<div class="align-items-center d-flex flex-grow-1 px-1">
|
||||
<div
|
||||
*ngFor="let dayItem of days; let i = index"
|
||||
class="day"
|
||||
[ngClass]="{
|
||||
'cursor-pointer valid': isDateOfInterest(
|
||||
itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1)
|
||||
),
|
||||
available:
|
||||
marketDataByMonth[itemByMonth.key][
|
||||
i + 1 < 10 ? '0' + (i + 1) : i + 1
|
||||
]?.marketPrice,
|
||||
today: isToday(
|
||||
itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1)
|
||||
)
|
||||
}"
|
||||
[title]="
|
||||
(itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1)
|
||||
| date: defaultDateFormat) ?? ''
|
||||
"
|
||||
(click)="
|
||||
onOpenMarketDataDetail({
|
||||
day: i + 1 < 10 ? '0' + (i + 1) : i + 1,
|
||||
yearMonth: itemByMonth.key
|
||||
})
|
||||
"
|
||||
></div>
|
||||
@for (itemByMonth of marketDataByMonth | keyvalue; track itemByMonth) {
|
||||
<div class="d-flex">
|
||||
<div class="date px-1 text-nowrap">{{ itemByMonth.key }}</div>
|
||||
<div class="align-items-center d-flex flex-grow-1 px-1">
|
||||
@for (dayItem of days; track dayItem; let i = $index) {
|
||||
<div
|
||||
class="day"
|
||||
[ngClass]="{
|
||||
'cursor-pointer valid': isDateOfInterest(
|
||||
itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1)
|
||||
),
|
||||
available:
|
||||
marketDataByMonth[itemByMonth.key][
|
||||
i + 1 < 10 ? '0' + (i + 1) : i + 1
|
||||
]?.marketPrice,
|
||||
today: isToday(
|
||||
itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1)
|
||||
)
|
||||
}"
|
||||
[title]="
|
||||
(itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1)
|
||||
| date: defaultDateFormat) ?? ''
|
||||
"
|
||||
(click)="
|
||||
onOpenMarketDataDetail({
|
||||
day: i + 1 < 10 ? '0' + (i + 1) : i + 1,
|
||||
yearMonth: itemByMonth.key
|
||||
})
|
||||
"
|
||||
></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -39,9 +39,13 @@
|
||||
</th>
|
||||
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
|
||||
<div class="text-truncate">{{ element.name }}</div>
|
||||
<div *ngIf="!isUUID(element.symbol)">
|
||||
<small class="text-muted">{{ element.symbol | gfSymbol }}</small>
|
||||
</div>
|
||||
@if (!isUUID(element.symbol)) {
|
||||
<div>
|
||||
<small class="text-muted">{{
|
||||
element.symbol | gfSymbol
|
||||
}}</small>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
||||
</ng-container>
|
||||
@ -121,11 +125,9 @@
|
||||
<ng-container matColumnDef="comment">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
<ion-icon
|
||||
*ngIf="element.comment"
|
||||
class="d-block"
|
||||
name="document-text-outline"
|
||||
/>
|
||||
@if (element.comment) {
|
||||
<ion-icon class="d-block" name="document-text-outline" />
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@ -222,15 +224,16 @@
|
||||
(page)="onChangePage($event)"
|
||||
/>
|
||||
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="isLoading && totalItems === 0"
|
||||
animation="pulse"
|
||||
class="px-4 py-3"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (isLoading && totalItems === 0) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="px-4 py-3"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -243,11 +243,11 @@
|
||||
<mat-label i18n>Asset Class</mat-label>
|
||||
<mat-select formControlName="assetClass">
|
||||
<mat-option [value]="null" />
|
||||
<mat-option
|
||||
*ngFor="let assetClass of assetClasses"
|
||||
[value]="assetClass.id"
|
||||
>{{ assetClass.label }}</mat-option
|
||||
>
|
||||
@for (assetClass of assetClasses; track assetClass) {
|
||||
<mat-option [value]="assetClass.id">{{
|
||||
assetClass.label
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -256,11 +256,11 @@
|
||||
<mat-label i18n>Asset Sub Class</mat-label>
|
||||
<mat-select formControlName="assetSubClass">
|
||||
<mat-option [value]="null" />
|
||||
<mat-option
|
||||
*ngFor="let assetSubClass of assetSubClasses"
|
||||
[value]="assetSubClass.id"
|
||||
>{{ assetSubClass.label }}</mat-option
|
||||
>
|
||||
@for (assetSubClass of assetSubClasses; track assetSubClass) {
|
||||
<mat-option [value]="assetSubClass.id">{{
|
||||
assetSubClass.label
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
@ -20,21 +20,24 @@
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div *ngIf="mode === 'auto'">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Name, symbol or ISIN</mat-label>
|
||||
<gf-symbol-autocomplete
|
||||
formControlName="searchSymbol"
|
||||
[includeIndices]="true"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="mode === 'manual'">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Symbol</mat-label>
|
||||
<input formControlName="addSymbol" matInput />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (mode === 'auto') {
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Name, symbol or ISIN</mat-label>
|
||||
<gf-symbol-autocomplete
|
||||
formControlName="searchSymbol"
|
||||
[includeIndices]="true"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
} @else if (mode === 'manual') {
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Symbol</mat-label>
|
||||
<input formControlName="addSymbol" matInput />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-end" mat-dialog-actions>
|
||||
<button i18n mat-button type="button" (click)="onCancel()">Cancel</button>
|
||||
|
@ -27,72 +27,77 @@
|
||||
[precision]="0"
|
||||
[value]="transactionCount"
|
||||
/>
|
||||
<div *ngIf="transactionCount && userCount">
|
||||
{{ transactionCount / userCount | number: '1.2-2' }}
|
||||
<span i18n>per User</span>
|
||||
</div>
|
||||
@if (transactionCount && userCount) {
|
||||
<div>
|
||||
{{ transactionCount / userCount | number: '1.2-2' }}
|
||||
<span i18n>per User</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="align-items-start d-flex my-3">
|
||||
<div class="w-50" i18n>Exchange Rates</div>
|
||||
<div class="w-50">
|
||||
<table>
|
||||
<tr *ngFor="let exchangeRate of exchangeRates">
|
||||
<td>
|
||||
<gf-value [locale]="user?.settings?.locale" [value]="1" />
|
||||
</td>
|
||||
<td class="pl-1">{{ exchangeRate.label1 }}</td>
|
||||
<td class="px-1">=</td>
|
||||
<td align="right">
|
||||
<gf-value
|
||||
class="d-inline-block"
|
||||
[locale]="user?.settings?.locale"
|
||||
[precision]="4"
|
||||
[value]="exchangeRate.value"
|
||||
/>
|
||||
</td>
|
||||
<td class="pl-1">{{ exchangeRate.label2 }}</td>
|
||||
<td>
|
||||
<button
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
[matMenuTriggerFor]="exchangeRateActionsMenu"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<ion-icon name="ellipsis-horizontal" />
|
||||
</button>
|
||||
<mat-menu
|
||||
#exchangeRateActionsMenu="matMenu"
|
||||
class="h-100 mx-1 no-min-width px-2"
|
||||
xPosition="before"
|
||||
>
|
||||
<a
|
||||
mat-menu-item
|
||||
[queryParams]="{
|
||||
assetProfileDialog: true,
|
||||
dataSource: exchangeRate.dataSource,
|
||||
symbol: exchangeRate.symbol
|
||||
}"
|
||||
[routerLink]="['/admin', 'market-data']"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="create-outline" />
|
||||
<span i18n>Edit</span>
|
||||
</span>
|
||||
</a>
|
||||
@for (exchangeRate of exchangeRates; track exchangeRate) {
|
||||
<tr>
|
||||
<td>
|
||||
<gf-value [locale]="user?.settings?.locale" [value]="1" />
|
||||
</td>
|
||||
<td class="pl-1">{{ exchangeRate.label1 }}</td>
|
||||
<td class="px-1">=</td>
|
||||
<td align="right">
|
||||
<gf-value
|
||||
class="d-inline-block"
|
||||
[locale]="user?.settings?.locale"
|
||||
[precision]="4"
|
||||
[value]="exchangeRate.value"
|
||||
/>
|
||||
</td>
|
||||
<td class="pl-1">{{ exchangeRate.label2 }}</td>
|
||||
<td>
|
||||
<button
|
||||
*ngIf="customCurrencies.includes(exchangeRate.label2)"
|
||||
mat-menu-item
|
||||
(click)="onDeleteCurrency(exchangeRate.label2)"
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
[matMenuTriggerFor]="exchangeRateActionsMenu"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="trash-outline" />
|
||||
<span i18n>Delete</span>
|
||||
</span>
|
||||
<ion-icon name="ellipsis-horizontal" />
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</tr>
|
||||
<mat-menu
|
||||
#exchangeRateActionsMenu="matMenu"
|
||||
class="h-100 mx-1 no-min-width px-2"
|
||||
xPosition="before"
|
||||
>
|
||||
<a
|
||||
mat-menu-item
|
||||
[queryParams]="{
|
||||
assetProfileDialog: true,
|
||||
dataSource: exchangeRate.dataSource,
|
||||
symbol: exchangeRate.symbol
|
||||
}"
|
||||
[routerLink]="['/admin', 'market-data']"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="create-outline" />
|
||||
<span i18n>Edit</span>
|
||||
</span>
|
||||
</a>
|
||||
@if (customCurrencies.includes(exchangeRate.label2)) {
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onDeleteCurrency(exchangeRate.label2)"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="trash-outline" />
|
||||
<span i18n>Delete</span>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<div class="mt-2">
|
||||
<button
|
||||
@ -119,17 +124,19 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="hasPermissionToToggleReadOnlyMode" class="d-flex my-3">
|
||||
<div class="w-50" i18n>Read-only Mode</div>
|
||||
<div class="w-50">
|
||||
<mat-slide-toggle
|
||||
color="primary"
|
||||
hideIcon="true"
|
||||
[checked]="info?.isReadOnlyMode"
|
||||
(change)="onReadOnlyModeChange($event)"
|
||||
/>
|
||||
@if (hasPermissionToToggleReadOnlyMode) {
|
||||
<div class="d-flex my-3">
|
||||
<div class="w-50" i18n>Read-only Mode</div>
|
||||
<div class="w-50">
|
||||
<mat-slide-toggle
|
||||
color="primary"
|
||||
hideIcon="true"
|
||||
[checked]="info?.isReadOnlyMode"
|
||||
(change)="onReadOnlyModeChange($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex my-3">
|
||||
<div class="w-50" i18n>Data Gathering</div>
|
||||
<div class="w-50">
|
||||
@ -141,99 +148,105 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="hasPermissionForSystemMessage" class="d-flex my-3">
|
||||
<div class="w-50" i18n>System Message</div>
|
||||
<div class="w-50">
|
||||
<div *ngIf="systemMessage" class="align-items-center d-flex">
|
||||
<div class="text-truncate">{{ systemMessage | json }}</div>
|
||||
<button
|
||||
class="h-100 mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
(click)="onDeleteSystemMessage()"
|
||||
>
|
||||
<ion-icon name="trash-outline" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
*ngIf="!info?.systemMessage"
|
||||
class="mt-2"
|
||||
color="accent"
|
||||
mat-flat-button
|
||||
(click)="onSetSystemMessage()"
|
||||
>
|
||||
<ion-icon class="mr-1" name="information-circle-outline" />
|
||||
<span i18n>Set Message</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="hasPermissionForSubscription"
|
||||
class="d-flex my-3 subscription"
|
||||
>
|
||||
<div class="w-50" i18n>Coupons</div>
|
||||
<div class="w-50">
|
||||
<table>
|
||||
<tr *ngFor="let coupon of coupons">
|
||||
<td class="text-monospace">{{ coupon.code }}</td>
|
||||
<td class="pl-2 text-right">{{ coupon.duration }}</td>
|
||||
<td>
|
||||
@if (hasPermissionForSystemMessage) {
|
||||
<div class="d-flex my-3">
|
||||
<div class="w-50" i18n>System Message</div>
|
||||
<div class="w-50">
|
||||
@if (systemMessage) {
|
||||
<div class="align-items-center d-flex">
|
||||
<div class="text-truncate">{{ systemMessage | json }}</div>
|
||||
<button
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
[matMenuTriggerFor]="couponActionsMenu"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<ion-icon name="ellipsis-horizontal" />
|
||||
</button>
|
||||
<mat-menu
|
||||
#couponActionsMenu="matMenu"
|
||||
class="h-100 mx-1 no-min-width px-2"
|
||||
xPosition="before"
|
||||
mat-button
|
||||
(click)="onDeleteSystemMessage()"
|
||||
>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onDeleteCoupon(coupon.code)"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="trash-outline" />
|
||||
<span i18n>Delete</span>
|
||||
</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="mt-2">
|
||||
<form #couponForm="ngForm" class="align-items-center d-flex">
|
||||
<mat-form-field
|
||||
appearance="outline"
|
||||
class="mr-2 without-hint"
|
||||
>
|
||||
<mat-select
|
||||
name="duration"
|
||||
[value]="couponDuration"
|
||||
(selectionChange)="onChangeCouponDuration($event.value)"
|
||||
>
|
||||
<mat-option value="7 days">7 Days</mat-option>
|
||||
<mat-option value="14 days">14 Days</mat-option>
|
||||
<mat-option value="30 days">30 Days</mat-option>
|
||||
<mat-option value="90 days">90 Days</mat-option>
|
||||
<mat-option value="180 days">180 Days</mat-option>
|
||||
<mat-option value="1 year">1 Year</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<ion-icon name="trash-outline" />
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@if (!info?.systemMessage) {
|
||||
<button
|
||||
class="mt-1"
|
||||
color="primary"
|
||||
class="mt-2"
|
||||
color="accent"
|
||||
mat-flat-button
|
||||
(click)="onAddCoupon()"
|
||||
(click)="onSetSystemMessage()"
|
||||
>
|
||||
<span i18n>Add</span>
|
||||
<ion-icon class="mr-1" name="information-circle-outline" />
|
||||
<span i18n>Set Message</span>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="d-flex my-3 subscription">
|
||||
<div class="w-50" i18n>Coupons</div>
|
||||
<div class="w-50">
|
||||
<table>
|
||||
@for (coupon of coupons; track coupon) {
|
||||
<tr>
|
||||
<td class="text-monospace">{{ coupon.code }}</td>
|
||||
<td class="pl-2 text-right">{{ coupon.duration }}</td>
|
||||
<td>
|
||||
<button
|
||||
class="mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
[matMenuTriggerFor]="couponActionsMenu"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
<ion-icon name="ellipsis-horizontal" />
|
||||
</button>
|
||||
<mat-menu
|
||||
#couponActionsMenu="matMenu"
|
||||
class="h-100 mx-1 no-min-width px-2"
|
||||
xPosition="before"
|
||||
>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onDeleteCoupon(coupon.code)"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="trash-outline" />
|
||||
<span i18n>Delete</span>
|
||||
</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<div class="mt-2">
|
||||
<form #couponForm="ngForm" class="align-items-center d-flex">
|
||||
<mat-form-field
|
||||
appearance="outline"
|
||||
class="mr-2 without-hint"
|
||||
>
|
||||
<mat-select
|
||||
name="duration"
|
||||
[value]="couponDuration"
|
||||
(selectionChange)="onChangeCouponDuration($event.value)"
|
||||
>
|
||||
<mat-option value="7 days">7 Days</mat-option>
|
||||
<mat-option value="14 days">14 Days</mat-option>
|
||||
<mat-option value="30 days">30 Days</mat-option>
|
||||
<mat-option value="90 days">90 Days</mat-option>
|
||||
<mat-option value="180 days">180 Days</mat-option>
|
||||
<mat-option value="1 year">1 Year</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button
|
||||
class="mt-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="onAddCoupon()"
|
||||
>
|
||||
<span i18n>Add</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex my-3">
|
||||
<div class="w-50" i18n>Housekeeping</div>
|
||||
<div class="w-50">
|
||||
|
@ -30,12 +30,13 @@
|
||||
<ng-container i18n>Name</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
<gf-asset-profile-icon
|
||||
*ngIf="element.url"
|
||||
class="d-inline mr-1"
|
||||
[tooltip]="element.name"
|
||||
[url]="element.url"
|
||||
/>
|
||||
@if (element.url) {
|
||||
<gf-asset-profile-icon
|
||||
class="d-inline mr-1"
|
||||
[tooltip]="element.name"
|
||||
[url]="element.url"
|
||||
/>
|
||||
}
|
||||
<span>{{ element.name }}</span>
|
||||
</td></ng-container
|
||||
>
|
||||
|
@ -4,8 +4,11 @@
|
||||
(keyup.enter)="platformForm.valid && onSubmit()"
|
||||
(ngSubmit)="onSubmit()"
|
||||
>
|
||||
<h1 *ngIf="data.platform.id" i18n mat-dialog-title>Update platform</h1>
|
||||
<h1 *ngIf="!data.platform.id" i18n mat-dialog-title>Add platform</h1>
|
||||
@if (data.platform.id) {
|
||||
<h1 i18n mat-dialog-title>Update platform</h1>
|
||||
} @else {
|
||||
<h1 i18n mat-dialog-title>Add platform</h1>
|
||||
}
|
||||
<div class="flex-grow-1 py-3" mat-dialog-content>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
|
@ -4,8 +4,11 @@
|
||||
(keyup.enter)="tagForm.valid && onSubmit()"
|
||||
(ngSubmit)="onSubmit()"
|
||||
>
|
||||
<h1 *ngIf="data.tag.id" i18n mat-dialog-title>Update tag</h1>
|
||||
<h1 *ngIf="!data.tag.id" i18n mat-dialog-title>Add tag</h1>
|
||||
@if (data.tag.id) {
|
||||
<h1 i18n mat-dialog-title>Update tag</h1>
|
||||
} @else {
|
||||
<h1 i18n mat-dialog-title>Add tag</h1>
|
||||
}
|
||||
<div class="flex-grow-1 py-3" mat-dialog-content>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
|
@ -49,43 +49,44 @@
|
||||
}"
|
||||
>{{ (element.id | slice: 0 : 5) + '...' }}</span
|
||||
>
|
||||
<gf-premium-indicator
|
||||
*ngIf="element?.subscription?.type === 'Premium'"
|
||||
class="ml-1"
|
||||
[enableLink]="false"
|
||||
[title]="
|
||||
'Expires ' +
|
||||
formatDistanceToNow(element.subscription.expiresAt) +
|
||||
' (' +
|
||||
(element.subscription.expiresAt | date: defaultDateFormat) +
|
||||
')'
|
||||
"
|
||||
/>
|
||||
@if (element?.subscription?.type === 'Premium') {
|
||||
<gf-premium-indicator
|
||||
class="ml-1"
|
||||
[enableLink]="false"
|
||||
[title]="
|
||||
'Expires ' +
|
||||
formatDistanceToNow(element.subscription.expiresAt) +
|
||||
' (' +
|
||||
(element.subscription.expiresAt
|
||||
| date: defaultDateFormat) +
|
||||
')'
|
||||
"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="hasPermissionForSubscription"
|
||||
matColumnDef="country"
|
||||
>
|
||||
<th
|
||||
*matHeaderCellDef
|
||||
class="mat-mdc-header-cell px-1 py-2"
|
||||
mat-header-cell
|
||||
>
|
||||
<ng-container i18n>Country</ng-container>
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element"
|
||||
class="mat-mdc-cell px-1 py-2"
|
||||
mat-cell
|
||||
>
|
||||
<span class="h5" [title]="element.country">{{
|
||||
getEmojiFlag(element.country)
|
||||
}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<ng-container matColumnDef="country">
|
||||
<th
|
||||
*matHeaderCellDef
|
||||
class="mat-mdc-header-cell px-1 py-2"
|
||||
mat-header-cell
|
||||
>
|
||||
<ng-container i18n>Country</ng-container>
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element"
|
||||
class="mat-mdc-cell px-1 py-2"
|
||||
mat-cell
|
||||
>
|
||||
<span class="h5" [title]="element.country">{{
|
||||
getEmojiFlag(element.country)
|
||||
}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="registration">
|
||||
<th
|
||||
@ -146,51 +147,49 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="hasPermissionForSubscription"
|
||||
matColumnDef="engagementPerDay"
|
||||
>
|
||||
<th
|
||||
*matHeaderCellDef
|
||||
class="mat-mdc-header-cell px-1 py-2 text-right"
|
||||
mat-header-cell
|
||||
>
|
||||
<ng-container i18n>Engagement per Day</ng-container>
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element"
|
||||
class="mat-mdc-cell px-1 py-2 text-right"
|
||||
mat-cell
|
||||
>
|
||||
<gf-value
|
||||
class="d-inline-block justify-content-end"
|
||||
[locale]="user?.settings?.locale"
|
||||
[precision]="0"
|
||||
[value]="element.engagement"
|
||||
/>
|
||||
</td>
|
||||
</ng-container>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<ng-container matColumnDef="engagementPerDay">
|
||||
<th
|
||||
*matHeaderCellDef
|
||||
class="mat-mdc-header-cell px-1 py-2 text-right"
|
||||
mat-header-cell
|
||||
>
|
||||
<ng-container i18n>Engagement per Day</ng-container>
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element"
|
||||
class="mat-mdc-cell px-1 py-2 text-right"
|
||||
mat-cell
|
||||
>
|
||||
<gf-value
|
||||
class="d-inline-block justify-content-end"
|
||||
[locale]="user?.settings?.locale"
|
||||
[precision]="0"
|
||||
[value]="element.engagement"
|
||||
/>
|
||||
</td>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container
|
||||
*ngIf="hasPermissionForSubscription"
|
||||
matColumnDef="lastRequest"
|
||||
>
|
||||
<th
|
||||
*matHeaderCellDef
|
||||
class="mat-mdc-header-cell px-1 py-2"
|
||||
i18n
|
||||
mat-header-cell
|
||||
>
|
||||
Last Request
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element"
|
||||
class="mat-mdc-cell px-1 py-2"
|
||||
mat-cell
|
||||
>
|
||||
{{ formatDistanceToNow(element.lastActivity) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<ng-container matColumnDef="lastRequest">
|
||||
<th
|
||||
*matHeaderCellDef
|
||||
class="mat-mdc-header-cell px-1 py-2"
|
||||
i18n
|
||||
mat-header-cell
|
||||
>
|
||||
Last Request
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element"
|
||||
class="mat-mdc-cell px-1 py-2"
|
||||
mat-cell
|
||||
>
|
||||
{{ formatDistanceToNow(element.lastActivity) }}
|
||||
</td>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th
|
||||
@ -212,16 +211,14 @@
|
||||
<ion-icon name="ellipsis-horizontal" />
|
||||
</button>
|
||||
<mat-menu #userMenu="matMenu" xPosition="before">
|
||||
<button
|
||||
*ngIf="hasPermissionToImpersonateAllUsers"
|
||||
mat-menu-item
|
||||
(click)="onImpersonateUser(element.id)"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="contract-outline" />
|
||||
<span i18n>Impersonate User</span>
|
||||
</span>
|
||||
</button>
|
||||
@if (hasPermissionToImpersonateAllUsers) {
|
||||
<button mat-menu-item (click)="onImpersonateUser(element.id)">
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="contract-outline" />
|
||||
<span i18n>Impersonate User</span>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="element.id === user?.id"
|
||||
|
@ -4,10 +4,9 @@
|
||||
class="align-items-center d-flex flex-grow-1 h5 mb-0 py-2 text-truncate"
|
||||
>
|
||||
<span i18n>Performance</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-xs-12 d-flex justify-content-end">
|
||||
@ -24,33 +23,33 @@
|
||||
(selectionChange)="onChangeBenchmark($event.value)"
|
||||
>
|
||||
<mat-option [value]="null" />
|
||||
<mat-option
|
||||
*ngFor="let symbolProfile of benchmarks"
|
||||
[value]="symbolProfile.id"
|
||||
>{{ symbolProfile.name }}</mat-option
|
||||
>
|
||||
<mat-option
|
||||
*ngIf="hasPermissionToAccessAdminControl"
|
||||
[routerLink]="['/admin', 'market-data']"
|
||||
>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2 text-muted" name="arrow-forward-outline" />
|
||||
<span i18n>Manage Benchmarks</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
@for (symbolProfile of benchmarks; track symbolProfile) {
|
||||
<mat-option [value]="symbolProfile.id">{{
|
||||
symbolProfile.name
|
||||
}}</mat-option>
|
||||
}
|
||||
@if (hasPermissionToAccessAdminControl) {
|
||||
<mat-option [routerLink]="['/admin', 'market-data']">
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2 text-muted" name="arrow-forward-outline" />
|
||||
<span i18n>Manage Benchmarks</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="isLoading"
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (isLoading) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
<canvas
|
||||
#chartCanvas
|
||||
class="h-100"
|
||||
|
@ -1,7 +1,5 @@
|
||||
<button
|
||||
*ngIf="deviceType === 'mobile'"
|
||||
mat-button
|
||||
(click)="onClickCloseButton()"
|
||||
>
|
||||
<ion-icon name="close" size="large" />
|
||||
</button>
|
||||
@if (deviceType === 'mobile') {
|
||||
<button mat-button (click)="onClickCloseButton()">
|
||||
<ion-icon name="close" size="large" />
|
||||
</button>
|
||||
}
|
||||
|
@ -3,11 +3,8 @@
|
||||
[ngClass]="{ 'text-center': position === 'center' }"
|
||||
>{{ title }}</span
|
||||
>
|
||||
<button
|
||||
*ngIf="deviceType !== 'mobile'"
|
||||
class="no-min-width px-0"
|
||||
mat-button
|
||||
(click)="onClickCloseButton()"
|
||||
>
|
||||
<ion-icon name="close" size="large" />
|
||||
</button>
|
||||
@if (deviceType !== 'mobile') {
|
||||
<button class="no-min-width px-0" mat-button (click)="onClickCloseButton()">
|
||||
<ion-icon name="close" size="large" />
|
||||
</button>
|
||||
}
|
||||
|
@ -12,12 +12,13 @@
|
||||
<small class="d-block" i18n>Current Market Mood</small>
|
||||
</div>
|
||||
</div>
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="!fearAndGreedIndex"
|
||||
animation="pulse"
|
||||
class="position-absolute w-100"
|
||||
[theme]="{
|
||||
height: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (!fearAndGreedIndex) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="position-absolute w-100"
|
||||
[theme]="{
|
||||
height: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<mat-toolbar class="px-0">
|
||||
<ng-container *ngIf="user">
|
||||
@if (user) {
|
||||
<div class="d-flex h-100 logo-container" [ngClass]="{ filled: hasTabs }">
|
||||
<a
|
||||
class="align-items-center justify-content-start rounded-0"
|
||||
@ -54,19 +54,21 @@
|
||||
>Accounts</a
|
||||
>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionToAccessAdminControl" class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === 'admin',
|
||||
'text-decoration-underline': currentRoute === 'admin'
|
||||
}"
|
||||
[routerLink]="['/admin']"
|
||||
>Admin Control</a
|
||||
>
|
||||
</li>
|
||||
@if (hasPermissionToAccessAdminControl) {
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === 'admin',
|
||||
'text-decoration-underline': currentRoute === 'admin'
|
||||
}"
|
||||
[routerLink]="['/admin']"
|
||||
>Admin Control</a
|
||||
>
|
||||
</li>
|
||||
}
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
@ -80,24 +82,23 @@
|
||||
>Resources</a
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="
|
||||
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
|
||||
"
|
||||
class="list-inline-item"
|
||||
>
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === routePricing,
|
||||
'text-decoration-underline': currentRoute === routePricing
|
||||
}"
|
||||
[routerLink]="routerLinkPricing"
|
||||
>Pricing</a
|
||||
>
|
||||
</li>
|
||||
@if (
|
||||
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
|
||||
) {
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === routePricing,
|
||||
'text-decoration-underline': currentRoute === routePricing
|
||||
}"
|
||||
[routerLink]="routerLinkPricing"
|
||||
>Pricing</a
|
||||
>
|
||||
</li>
|
||||
}
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
@ -111,42 +112,44 @@
|
||||
>About</a
|
||||
>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionToAccessAssistant" class="list-inline-item">
|
||||
<button
|
||||
#assistantTrigger="matMenuTrigger"
|
||||
class="h-100 no-min-width px-2"
|
||||
mat-button
|
||||
matBadge="✓"
|
||||
matBadgeSize="small"
|
||||
[mat-menu-trigger-for]="assistantMenu"
|
||||
[matBadgeHidden]="
|
||||
!hasFilters || !user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[matMenuTriggerRestoreFocus]="false"
|
||||
(menuOpened)="onOpenAssistant()"
|
||||
>
|
||||
<ion-icon class="rotate-90" name="options-outline" />
|
||||
</button>
|
||||
<mat-menu
|
||||
#assistantMenu="matMenu"
|
||||
class="assistant"
|
||||
xPosition="before"
|
||||
[overlapTrigger]="true"
|
||||
(closed)="assistantElement?.setIsOpen(false)"
|
||||
>
|
||||
<gf-assistant
|
||||
#assistant
|
||||
[deviceType]="deviceType"
|
||||
[hasPermissionToAccessAdminControl]="
|
||||
hasPermissionToAccessAdminControl
|
||||
@if (hasPermissionToAccessAssistant) {
|
||||
<li class="list-inline-item">
|
||||
<button
|
||||
#assistantTrigger="matMenuTrigger"
|
||||
class="h-100 no-min-width px-2"
|
||||
mat-button
|
||||
matBadge="✓"
|
||||
matBadgeSize="small"
|
||||
[mat-menu-trigger-for]="assistantMenu"
|
||||
[matBadgeHidden]="
|
||||
!hasFilters || !user?.settings?.isExperimentalFeatures
|
||||
"
|
||||
[user]="user"
|
||||
(closed)="closeAssistant()"
|
||||
(dateRangeChanged)="onDateRangeChange($event)"
|
||||
(filtersChanged)="onFiltersChanged($event)"
|
||||
/>
|
||||
</mat-menu>
|
||||
</li>
|
||||
[matMenuTriggerRestoreFocus]="false"
|
||||
(menuOpened)="onOpenAssistant()"
|
||||
>
|
||||
<ion-icon class="rotate-90" name="options-outline" />
|
||||
</button>
|
||||
<mat-menu
|
||||
#assistantMenu="matMenu"
|
||||
class="assistant"
|
||||
xPosition="before"
|
||||
[overlapTrigger]="true"
|
||||
(closed)="assistantElement?.setIsOpen(false)"
|
||||
>
|
||||
<gf-assistant
|
||||
#assistant
|
||||
[deviceType]="deviceType"
|
||||
[hasPermissionToAccessAdminControl]="
|
||||
hasPermissionToAccessAdminControl
|
||||
"
|
||||
[user]="user"
|
||||
(closed)="closeAssistant()"
|
||||
(dateRangeChanged)="onDateRangeChange($event)"
|
||||
(filtersChanged)="onFiltersChanged($event)"
|
||||
/>
|
||||
</mat-menu>
|
||||
</li>
|
||||
}
|
||||
<li class="list-inline-item">
|
||||
<button
|
||||
class="no-min-width px-1"
|
||||
@ -167,40 +170,31 @@
|
||||
/>
|
||||
</button>
|
||||
<mat-menu #accountMenu="matMenu" xPosition="before">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
hasPermissionForSubscription &&
|
||||
user?.subscription?.type === 'Basic'
|
||||
"
|
||||
>
|
||||
@if (
|
||||
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
|
||||
) {
|
||||
<a class="d-flex" mat-menu-item [routerLink]="routerLinkPricing"
|
||||
><span class="align-items-center d-flex"
|
||||
><span
|
||||
><ng-container
|
||||
*ngIf="user.subscription.offer === 'default'"
|
||||
i18n
|
||||
>Upgrade Plan</ng-container
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
user.subscription.offer === 'renewal' ||
|
||||
user.subscription.offer === 'renewal-early-bird'
|
||||
"
|
||||
i18n
|
||||
>Renew Plan</ng-container
|
||||
></span
|
||||
>
|
||||
><span>
|
||||
@if (user.subscription.offer === 'default') {
|
||||
<ng-container i18n>Upgrade Plan</ng-container>
|
||||
} @else if (
|
||||
user.subscription.offer === 'renewal' ||
|
||||
user.subscription.offer === 'renewal-early-bird'
|
||||
) {
|
||||
<ng-container i18n>Renew Plan</ng-container>
|
||||
}
|
||||
</span>
|
||||
<gf-premium-indicator
|
||||
class="d-inline-block ml-1"
|
||||
[enableLink]="false" /></span
|
||||
></a>
|
||||
<hr class="m-0" />
|
||||
</ng-container>
|
||||
<ng-container *ngIf="user?.access?.length > 0">
|
||||
}
|
||||
@if (user?.access?.length > 0) {
|
||||
<button mat-menu-item (click)="impersonateAccount(null)">
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
*ngIf="user?.access?.length > 0"
|
||||
class="mr-2"
|
||||
[name]="
|
||||
impersonationId
|
||||
@ -211,27 +205,28 @@
|
||||
<span i18n>Me</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
*ngFor="let accessItem of user?.access"
|
||||
mat-menu-item
|
||||
(click)="impersonateAccount(accessItem.id)"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="mr-2"
|
||||
name="square-outline"
|
||||
[name]="
|
||||
accessItem.id === impersonationId
|
||||
? 'radio-button-on-outline'
|
||||
: 'radio-button-off-outline'
|
||||
"
|
||||
/>
|
||||
<span *ngIf="accessItem.alias">{{ accessItem.alias }}</span>
|
||||
<span *ngIf="!accessItem.alias" i18n>User</span>
|
||||
</span>
|
||||
</button>
|
||||
@for (accessItem of user?.access; track accessItem) {
|
||||
<button mat-menu-item (click)="impersonateAccount(accessItem.id)">
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="mr-2"
|
||||
name="square-outline"
|
||||
[name]="
|
||||
accessItem.id === impersonationId
|
||||
? 'radio-button-on-outline'
|
||||
: 'radio-button-off-outline'
|
||||
"
|
||||
/>
|
||||
@if (accessItem.alias) {
|
||||
<span>{{ accessItem.alias }}</span>
|
||||
} @else {
|
||||
<span i18n>User</span>
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
<hr class="m-0" />
|
||||
</ng-container>
|
||||
}
|
||||
<a
|
||||
class="d-flex d-sm-none"
|
||||
i18n
|
||||
@ -268,15 +263,16 @@
|
||||
[routerLink]="['/account']"
|
||||
>My Ghostfolio</a
|
||||
>
|
||||
<a
|
||||
*ngIf="hasPermissionToAccessAdminControl"
|
||||
class="d-flex d-sm-none"
|
||||
i18n
|
||||
mat-menu-item
|
||||
[ngClass]="{ 'font-weight-bold': currentRoute === 'admin' }"
|
||||
[routerLink]="['/admin']"
|
||||
>Admin Control</a
|
||||
>
|
||||
@if (hasPermissionToAccessAdminControl) {
|
||||
<a
|
||||
class="d-flex d-sm-none"
|
||||
i18n
|
||||
mat-menu-item
|
||||
[ngClass]="{ 'font-weight-bold': currentRoute === 'admin' }"
|
||||
[routerLink]="['/admin']"
|
||||
>Admin Control</a
|
||||
>
|
||||
}
|
||||
<hr class="m-0" />
|
||||
<a
|
||||
class="d-flex d-sm-none"
|
||||
@ -288,18 +284,18 @@
|
||||
[routerLink]="routerLinkResources"
|
||||
>Resources</a
|
||||
>
|
||||
<a
|
||||
*ngIf="
|
||||
hasPermissionForSubscription &&
|
||||
user?.subscription?.type === 'Basic'
|
||||
"
|
||||
class="d-flex d-sm-none"
|
||||
i18n
|
||||
mat-menu-item
|
||||
[ngClass]="{ 'font-weight-bold': currentRoute === routePricing }"
|
||||
[routerLink]="routerLinkPricing"
|
||||
>Pricing</a
|
||||
>
|
||||
@if (
|
||||
hasPermissionForSubscription && user?.subscription?.type === 'Basic'
|
||||
) {
|
||||
<a
|
||||
class="d-flex d-sm-none"
|
||||
i18n
|
||||
mat-menu-item
|
||||
[ngClass]="{ 'font-weight-bold': currentRoute === routePricing }"
|
||||
[routerLink]="routerLinkPricing"
|
||||
>Pricing</a
|
||||
>
|
||||
}
|
||||
<a
|
||||
class="d-flex d-sm-none"
|
||||
i18n
|
||||
@ -313,8 +309,8 @@
|
||||
</mat-menu>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="user === null">
|
||||
}
|
||||
@if (user === null) {
|
||||
<div class="d-flex h-100 logo-container" [ngClass]="{ filled: hasTabs }">
|
||||
<a
|
||||
class="align-items-center justify-content-start rounded-0"
|
||||
@ -357,35 +353,36 @@
|
||||
>About</a
|
||||
>
|
||||
</li>
|
||||
<li *ngIf="hasPermissionForSubscription" class="list-inline-item">
|
||||
<a
|
||||
class="d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === routePricing,
|
||||
'text-decoration-underline': currentRoute === routePricing
|
||||
}"
|
||||
[routerLink]="routerLinkPricing"
|
||||
>Pricing</a
|
||||
>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="hasPermissionToAccessFearAndGreedIndex"
|
||||
class="list-inline-item"
|
||||
>
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === routeMarkets,
|
||||
'text-decoration-underline': currentRoute === routeMarkets
|
||||
}"
|
||||
[routerLink]="routerLinkMarkets"
|
||||
>Markets</a
|
||||
>
|
||||
</li>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === routePricing,
|
||||
'text-decoration-underline': currentRoute === routePricing
|
||||
}"
|
||||
[routerLink]="routerLinkPricing"
|
||||
>Pricing</a
|
||||
>
|
||||
</li>
|
||||
}
|
||||
@if (hasPermissionToAccessFearAndGreedIndex) {
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[ngClass]="{
|
||||
'font-weight-bold': currentRoute === routeMarkets,
|
||||
'text-decoration-underline': currentRoute === routeMarkets
|
||||
}"
|
||||
[routerLink]="routerLinkMarkets"
|
||||
>Markets</a
|
||||
>
|
||||
</li>
|
||||
}
|
||||
<li class="list-inline-item">
|
||||
<a
|
||||
class="d-none d-sm-block no-min-width p-1"
|
||||
@ -399,18 +396,17 @@
|
||||
<ng-container i18n>Sign in</ng-container>
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="currentRoute !== 'register' && hasPermissionToCreateUser"
|
||||
class="list-inline-item ml-1"
|
||||
>
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="routerLinkRegister"
|
||||
><ng-container i18n>Get started</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
@if (currentRoute !== 'register' && hasPermissionToCreateUser) {
|
||||
<li class="list-inline-item ml-1">
|
||||
<a
|
||||
class="d-none d-sm-block"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="routerLinkRegister"
|
||||
><ng-container i18n>Get started</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</ng-container>
|
||||
}
|
||||
</mat-toolbar>
|
||||
|
@ -376,9 +376,9 @@
|
||||
<div class="col">
|
||||
<div class="h5" i18n>Tags</div>
|
||||
<mat-chip-listbox>
|
||||
<mat-chip-option *ngFor="let tag of tags" disabled>{{
|
||||
tag.name
|
||||
}}</mat-chip-option>
|
||||
@for (tag of tags; track tag) {
|
||||
<mat-chip-option disabled>{{ tag.name }}</mat-chip-option>
|
||||
}
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,30 +1,32 @@
|
||||
<div class="container">
|
||||
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1>
|
||||
<div *ngIf="hasPermissionToAccessFearAndGreedIndex" class="mb-5 row">
|
||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||
<div class="mb-2 text-center text-muted">
|
||||
<small i18n>Last {{ numberOfDays }} Days</small>
|
||||
@if (hasPermissionToAccessFearAndGreedIndex) {
|
||||
<div class="mb-5 row">
|
||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||
<div class="mb-2 text-center text-muted">
|
||||
<small i18n>Last {{ numberOfDays }} Days</small>
|
||||
</div>
|
||||
<gf-line-chart
|
||||
class="mb-3"
|
||||
symbol="Fear & Greed Index"
|
||||
[colorScheme]="user?.settings?.colorScheme"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isAnimated]="true"
|
||||
[locale]="user?.settings?.locale || undefined"
|
||||
[showXAxis]="true"
|
||||
[showYAxis]="true"
|
||||
[yMax]="100"
|
||||
[yMaxLabel]="greedLabel"
|
||||
[yMin]="0"
|
||||
[yMinLabel]="fearLabel"
|
||||
/>
|
||||
<gf-fear-and-greed-index
|
||||
class="d-flex justify-content-center"
|
||||
[fearAndGreedIndex]="fearAndGreedIndex"
|
||||
/>
|
||||
</div>
|
||||
<gf-line-chart
|
||||
class="mb-3"
|
||||
symbol="Fear & Greed Index"
|
||||
[colorScheme]="user?.settings?.colorScheme"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isAnimated]="true"
|
||||
[locale]="user?.settings?.locale || undefined"
|
||||
[showXAxis]="true"
|
||||
[showYAxis]="true"
|
||||
[yMax]="100"
|
||||
[yMaxLabel]="greedLabel"
|
||||
[yMin]="0"
|
||||
[yMinLabel]="fearLabel"
|
||||
/>
|
||||
<gf-fear-and-greed-index
|
||||
class="d-flex justify-content-center"
|
||||
[fearAndGreedIndex]="fearAndGreedIndex"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-xs-12 col-md-8 offset-md-2">
|
||||
@ -33,15 +35,16 @@
|
||||
[locale]="user?.settings?.locale || undefined"
|
||||
[user]="user"
|
||||
/>
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="isLoading"
|
||||
animation="pulse"
|
||||
class="px-2 py-3"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (isLoading) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="px-2 py-3"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,22 +39,19 @@
|
||||
</li>
|
||||
</ol>
|
||||
<div class="d-flex justify-content-center">
|
||||
<a
|
||||
*ngIf="user?.accounts?.length === 1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/accounts']"
|
||||
>
|
||||
<ng-container i18n>Setup accounts</ng-container>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="user?.accounts?.length > 1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>
|
||||
<ng-container i18n>Add activity</ng-container>
|
||||
</a>
|
||||
@if (user?.accounts?.length === 1) {
|
||||
<a color="primary" mat-flat-button [routerLink]="['/accounts']">
|
||||
<ng-container i18n>Setup accounts</ng-container>
|
||||
</a>
|
||||
} @else if (user?.accounts?.length > 1) {
|
||||
<a
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>
|
||||
<ng-container i18n>Add activity</ng-container>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,12 @@
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="isLoading"
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (isLoading) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
<canvas
|
||||
#chartCanvas
|
||||
class="h-100"
|
||||
|
@ -27,7 +27,7 @@
|
||||
</button>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<ng-container *ngIf="data.hasPermissionToUseSocialLogin">
|
||||
@if (data.hasPermissionToUseSocialLogin) {
|
||||
<div class="my-3 text-center text-muted" i18n>or</div>
|
||||
<div class="d-flex flex-column">
|
||||
<button
|
||||
@ -52,7 +52,7 @@
|
||||
/><span i18n>Sign in with Google</span></a
|
||||
>
|
||||
</div>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
|
@ -10,16 +10,18 @@
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div *ngIf="isLoading" class="align-items-center d-flex">
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="mb-2"
|
||||
[theme]="{
|
||||
height: '4rem',
|
||||
width: '15rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
@if (isLoading) {
|
||||
<div class="align-items-center d-flex">
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="mb-2"
|
||||
[theme]="{
|
||||
height: '4rem',
|
||||
width: '15rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
class="display-4 font-weight-bold m-0 text-center value-container"
|
||||
[hidden]="isLoading"
|
||||
@ -34,28 +36,32 @@
|
||||
{{ unit }}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showDetails" class="row">
|
||||
<div class="d-flex col justify-content-end">
|
||||
<gf-value
|
||||
[colorizeSign]="true"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="
|
||||
isLoading ? undefined : performance?.netPerformanceWithCurrencyEffect
|
||||
"
|
||||
/>
|
||||
@if (showDetails) {
|
||||
<div class="row">
|
||||
<div class="d-flex col justify-content-end">
|
||||
<gf-value
|
||||
[colorizeSign]="true"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="
|
||||
isLoading
|
||||
? undefined
|
||||
: performance?.netPerformanceWithCurrencyEffect
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<gf-value
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="locale"
|
||||
[value]="
|
||||
isLoading
|
||||
? undefined
|
||||
: performance?.netPerformancePercentageWithCurrencyEffect
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<gf-value
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="locale"
|
||||
[value]="
|
||||
isLoading
|
||||
? undefined
|
||||
: performance?.netPerformancePercentageWithCurrencyEffect
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,43 +1,51 @@
|
||||
<div class="py-3">
|
||||
<div class="align-items-center flex-nowrap no-gutters row">
|
||||
<div *ngIf="isLoading">
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="mr-2"
|
||||
[theme]="{
|
||||
height: '2rem',
|
||||
width: '2rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!isLoading"
|
||||
class="align-items-center d-flex icon-container mr-2 px-2"
|
||||
[ngClass]="{ okay: rule?.value === true, warn: rule?.value === false }"
|
||||
>
|
||||
<ion-icon *ngIf="rule?.value === true" name="checkmark-circle-outline" />
|
||||
<ion-icon *ngIf="rule?.value === false" name="warning-outline" />
|
||||
</div>
|
||||
<div *ngIf="isLoading" class="flex-grow-1">
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="mt-1 mb-1"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '10rem'
|
||||
}"
|
||||
/>
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '15rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div *ngIf="!isLoading" class="flex-grow-1">
|
||||
<div class="h6 my-1">{{ rule?.name }}</div>
|
||||
<div class="evaluation">{{ rule?.evaluation }}</div>
|
||||
</div>
|
||||
@if (isLoading) {
|
||||
<div>
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="mr-2"
|
||||
[theme]="{
|
||||
height: '2rem',
|
||||
width: '2rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
class="align-items-center d-flex icon-container mr-2 px-2"
|
||||
[ngClass]="{ okay: rule?.value === true, warn: rule?.value === false }"
|
||||
>
|
||||
@if (rule?.value === true) {
|
||||
<ion-icon name="checkmark-circle-outline" />
|
||||
} @else {
|
||||
<ion-icon name="warning-outline" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (isLoading) {
|
||||
<div class="flex-grow-1">
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="mt-1 mb-1"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '10rem'
|
||||
}"
|
||||
/>
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '15rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex-grow-1">
|
||||
<div class="h6 my-1">{{ rule?.name }}</div>
|
||||
<div class="evaluation">{{ rule?.evaluation }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,20 +1,22 @@
|
||||
<div class="container p-0">
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
<mat-card
|
||||
*ngIf="hasPermissionToCreateOrder && rules === null"
|
||||
appearance="outlined"
|
||||
class="my-2 text-center"
|
||||
>
|
||||
<mat-card-content>
|
||||
<gf-no-transactions-info-indicator [hasBorder]="false" />
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@if (hasPermissionToCreateOrder && rules === null) {
|
||||
<mat-card appearance="outlined" class="my-2 text-center">
|
||||
<mat-card-content>
|
||||
<gf-no-transactions-info-indicator [hasBorder]="false" />
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
|
||||
<gf-rule *ngIf="rules?.length === 0" [isLoading]="true" />
|
||||
<ng-container *ngIf="rules !== null && rules !== undefined">
|
||||
<gf-rule *ngFor="let rule of rules" [rule]="rule" />
|
||||
</ng-container>
|
||||
@if (rules?.length === 0) {
|
||||
<gf-rule [isLoading]="true" />
|
||||
}
|
||||
@if (rules !== null && rules !== undefined) {
|
||||
@for (rule of rules; track rule) {
|
||||
<gf-rule [rule]="rule" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,12 +3,13 @@
|
||||
[formControl]="optionFormControl"
|
||||
(change)="onValueChange()"
|
||||
>
|
||||
<mat-radio-button
|
||||
*ngFor="let option of options"
|
||||
class="d-inline-flex"
|
||||
[disabled]="isLoading"
|
||||
[ngClass]="{ 'cursor-pointer': !isLoading }"
|
||||
[value]="option.value"
|
||||
>{{ option.label }}</mat-radio-button
|
||||
>
|
||||
@for (option of options; track option) {
|
||||
<mat-radio-button
|
||||
class="d-inline-flex"
|
||||
[disabled]="isLoading"
|
||||
[ngClass]="{ 'cursor-pointer': !isLoading }"
|
||||
[value]="option.value"
|
||||
>{{ option.label }}</mat-radio-button
|
||||
>
|
||||
}
|
||||
</mat-radio-group>
|
||||
|
@ -3,25 +3,26 @@
|
||||
class="align-items-center d-none d-sm-flex h3 justify-content-center mb-3 text-center"
|
||||
>
|
||||
<span i18n>Granted Access</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h1>
|
||||
<gf-access-table
|
||||
[accesses]="accesses"
|
||||
[showActions]="hasPermissionToDeleteAccess"
|
||||
(accessDeleted)="onDeleteAccess($event)"
|
||||
/>
|
||||
<div *ngIf="hasPermissionToCreateAccess" class="fab-container">
|
||||
<a
|
||||
class="align-items-center d-flex justify-content-center"
|
||||
color="primary"
|
||||
mat-fab
|
||||
[queryParams]="{ createDialog: true }"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<ion-icon name="add-outline" size="large" />
|
||||
</a>
|
||||
</div>
|
||||
@if (hasPermissionToCreateAccess) {
|
||||
<div class="fab-container">
|
||||
<a
|
||||
class="align-items-center d-flex justify-content-center"
|
||||
color="primary"
|
||||
mat-fab
|
||||
[queryParams]="{ createDialog: true }"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<ion-icon name="add-outline" size="large" />
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -6,64 +6,57 @@
|
||||
[expiresAt]="user?.subscription?.expiresAt | date: defaultDateFormat"
|
||||
[name]="user?.subscription?.type"
|
||||
/>
|
||||
<div
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="d-flex flex-column mt-5"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<div class="d-flex flex-column mt-5">
|
||||
@if (
|
||||
hasPermissionForSubscription && hasPermissionToUpdateUserSettings
|
||||
"
|
||||
>
|
||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||
<ng-container *ngIf="user.subscription.offer === 'default'" i18n
|
||||
>Upgrade Plan</ng-container
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
) {
|
||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||
@if (user.subscription.offer === 'default') {
|
||||
<ng-container i18n>Upgrade Plan</ng-container>
|
||||
} @else if (
|
||||
user.subscription.offer === 'renewal' ||
|
||||
user.subscription.offer === 'renewal-early-bird'
|
||||
"
|
||||
i18n
|
||||
>Renew Plan</ng-container
|
||||
>
|
||||
</button>
|
||||
<div *ngIf="price" class="mt-1 text-center">
|
||||
<ng-container *ngIf="coupon"
|
||||
><del class="text-muted"
|
||||
>{{ baseCurrency }} {{ price }}</del
|
||||
> {{ baseCurrency }} {{
|
||||
price - coupon
|
||||
}}</ng-container
|
||||
>
|
||||
<ng-container *ngIf="!coupon"
|
||||
>{{ baseCurrency }} {{ price }}</ng-container
|
||||
> <span i18n>per year</span>
|
||||
) {
|
||||
<ng-container i18n>Renew Plan</ng-container>
|
||||
}
|
||||
</button>
|
||||
@if (price) {
|
||||
<div class="mt-1 text-center">
|
||||
@if (coupon) {
|
||||
<del class="text-muted"
|
||||
>{{ baseCurrency }} {{ price }}</del
|
||||
> {{ baseCurrency }} {{ price - coupon }}
|
||||
} @else {
|
||||
{{ baseCurrency }} {{ price }}
|
||||
}
|
||||
<span i18n>per year</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="align-items-center d-flex justify-content-center mt-4">
|
||||
@if (!user?.subscription?.expiresAt) {
|
||||
<a class="mx-1" mat-stroked-button [href]="trySubscriptionMail"
|
||||
><span i18n>Try Premium</span>
|
||||
<gf-premium-indicator
|
||||
class="d-inline-block ml-1"
|
||||
[enableLink]="false"
|
||||
/>
|
||||
</a>
|
||||
}
|
||||
@if (hasPermissionToUpdateUserSettings) {
|
||||
<a
|
||||
class="mx-1"
|
||||
i18n
|
||||
mat-stroked-button
|
||||
[routerLink]=""
|
||||
(click)="onRedeemCoupon()"
|
||||
>Redeem Coupon</a
|
||||
>
|
||||
}
|
||||
</div>
|
||||
</ng-container>
|
||||
<div class="align-items-center d-flex justify-content-center mt-4">
|
||||
<a
|
||||
*ngIf="!user?.subscription?.expiresAt"
|
||||
class="mx-1"
|
||||
mat-stroked-button
|
||||
[href]="trySubscriptionMail"
|
||||
><span i18n>Try Premium</span>
|
||||
<gf-premium-indicator
|
||||
class="d-inline-block ml-1"
|
||||
[enableLink]="false"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="hasPermissionToUpdateUserSettings"
|
||||
class="mx-1"
|
||||
i18n
|
||||
mat-stroked-button
|
||||
[routerLink]=""
|
||||
(click)="onRedeemCoupon()"
|
||||
>Redeem Coupon</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,11 +36,9 @@
|
||||
onChangeUserSetting('baseCurrency', $event.value)
|
||||
"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let currency of currencies"
|
||||
[value]="currency"
|
||||
>{{ currency }}</mat-option
|
||||
>
|
||||
@for (currency of currencies; track currency) {
|
||||
<mat-option [value]="currency">{{ currency }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -48,20 +46,18 @@
|
||||
<div class="align-items-center d-flex mb-2">
|
||||
<div class="pr-1 w-50">
|
||||
<div i18n>Language</div>
|
||||
<div
|
||||
*ngIf="isCommunityLanguage()"
|
||||
class="hint-text text-muted"
|
||||
i18n
|
||||
>
|
||||
If a translation is missing, kindly support us in extending it
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/apps/client/src/locales/messages.{{
|
||||
language
|
||||
}}.xlf"
|
||||
target="_blank"
|
||||
>here</a
|
||||
>.
|
||||
</div>
|
||||
@if (isCommunityLanguage()) {
|
||||
<div class="hint-text text-muted" i18n>
|
||||
If a translation is missing, kindly support us in extending it
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/apps/client/src/locales/messages.{{
|
||||
language
|
||||
}}.xlf"
|
||||
target="_blank"
|
||||
>here</a
|
||||
>.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="pl-1 w-50">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
@ -134,9 +130,9 @@
|
||||
"
|
||||
>
|
||||
<mat-option [value]="null" />
|
||||
<mat-option *ngFor="let locale of locales" [value]="locale">{{
|
||||
locale
|
||||
}}</mat-option>
|
||||
@for (locale of locales; track locale) {
|
||||
<mat-option [value]="locale">{{ locale }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -198,26 +194,25 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="hasPermissionToUpdateUserSettings"
|
||||
class="align-items-center d-flex mt-4 py-1"
|
||||
>
|
||||
<div class="pr-1 w-50">
|
||||
<div i18n>Experimental Features</div>
|
||||
<div class="hint-text text-muted" i18n>
|
||||
Sneak peek at upcoming functionality
|
||||
@if (hasPermissionToUpdateUserSettings) {
|
||||
<div class="align-items-center d-flex mt-4 py-1">
|
||||
<div class="pr-1 w-50">
|
||||
<div i18n>Experimental Features</div>
|
||||
<div class="hint-text text-muted" i18n>
|
||||
Sneak peek at upcoming functionality
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-1 w-50">
|
||||
<mat-slide-toggle
|
||||
color="primary"
|
||||
hideIcon="true"
|
||||
[checked]="user.settings.isExperimentalFeatures"
|
||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||
(change)="onExperimentalFeaturesChange($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-1 w-50">
|
||||
<mat-slide-toggle
|
||||
color="primary"
|
||||
hideIcon="true"
|
||||
[checked]="user.settings.isExperimentalFeatures"
|
||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||
(change)="onExperimentalFeaturesChange($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="align-items-center d-flex mt-4 py-1">
|
||||
<div class="pr-1 w-50">
|
||||
Ghostfolio <ng-container i18n>User ID</ng-container>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="isLoading"
|
||||
animation="pulse"
|
||||
class="h-100"
|
||||
[theme]="{
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (isLoading) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="h-100"
|
||||
[theme]="{
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
|
||||
<div class="align-items-center d-flex h-100 w-100" id="svgMap"></div>
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
@ -21,11 +21,12 @@
|
||||
title="GNU Affero General Public License"
|
||||
>AGPL-3.0 license</a
|
||||
>
|
||||
<ng-container *ngIf="hasPermissionForStatistics">
|
||||
@if (hasPermissionForStatistics) {
|
||||
and we share aggregated
|
||||
<a title="Open Startup" [routerLink]="['/open']">key metrics</a>
|
||||
of the platform’s performance</ng-container
|
||||
>. The project has been initiated by
|
||||
of the platform’s performance
|
||||
}
|
||||
. The project has been initiated by
|
||||
<a href="https://dotsilver.ch" title="Website of Thomas Kaul"
|
||||
>Thomas Kaul</a
|
||||
>
|
||||
@ -35,12 +36,12 @@
|
||||
title="Contributors to Ghostfolio"
|
||||
>contributors</a
|
||||
>.
|
||||
<ng-container *ngIf="hasPermissionForSubscription"
|
||||
>Check the system status at
|
||||
@if (hasPermissionForSubscription) {
|
||||
Check the system status at
|
||||
<a href="https://status.ghostfol.io" title="Ghostfolio Status"
|
||||
>status.ghostfol.io</a
|
||||
>.</ng-container
|
||||
>
|
||||
>.
|
||||
}
|
||||
</p>
|
||||
<p>
|
||||
If you encounter a bug or would like to suggest an improvement or a
|
||||
@ -57,12 +58,13 @@
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>, send an e-mail to
|
||||
>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
, send an e-mail to
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
>
|
||||
}
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
@ -79,15 +81,16 @@
|
||||
>
|
||||
<ion-icon name="logo-x" />
|
||||
</a>
|
||||
<a
|
||||
*ngIf="user?.subscription?.type === 'Premium'"
|
||||
class="mx-2"
|
||||
href="mailto:hi@ghostfol.io"
|
||||
mat-icon-button
|
||||
title="Send an e-mail"
|
||||
>
|
||||
<ion-icon name="mail" />
|
||||
</a>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
<a
|
||||
class="mx-2"
|
||||
href="mailto:hi@ghostfol.io"
|
||||
mat-icon-button
|
||||
title="Send an e-mail"
|
||||
>
|
||||
<ion-icon name="mail" />
|
||||
</a>
|
||||
}
|
||||
<a
|
||||
class="mx-2"
|
||||
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
|
||||
@ -105,29 +108,26 @@
|
||||
<ion-icon name="logo-github" />
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
*ngIf="hasPermissionForSubscription"
|
||||
class="d-flex justify-content-center"
|
||||
>
|
||||
<div
|
||||
class="independent-and-bootstrapped-logo mb-2"
|
||||
title="Ghostfolio is an independent & bootstrapped business"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!hasPermissionForSubscription"
|
||||
class="d-flex justify-content-center"
|
||||
>
|
||||
<a
|
||||
href="https://www.buymeacoffee.com/ghostfolio"
|
||||
target="_blank"
|
||||
title="Support Ghostfolio"
|
||||
><img
|
||||
class="mb-2"
|
||||
src="../assets/images/button-buy-me-a-coffee.png"
|
||||
width="180"
|
||||
/></a>
|
||||
</div>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="d-flex justify-content-center">
|
||||
<div
|
||||
class="independent-and-bootstrapped-logo mb-2"
|
||||
title="Ghostfolio is an independent & bootstrapped business"
|
||||
></div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="d-flex justify-content-center">
|
||||
<a
|
||||
href="https://www.buymeacoffee.com/ghostfolio"
|
||||
target="_blank"
|
||||
title="Support Ghostfolio"
|
||||
><img
|
||||
class="mb-2"
|
||||
src="../assets/images/button-buy-me-a-coffee.png"
|
||||
width="180"
|
||||
/></a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -22,22 +22,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToCreateAccount &&
|
||||
!user.settings.isRestrictedView
|
||||
"
|
||||
class="fab-container"
|
||||
>
|
||||
<a
|
||||
class="align-items-center d-flex justify-content-center"
|
||||
color="primary"
|
||||
mat-fab
|
||||
[queryParams]="{ createDialog: true }"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<ion-icon name="add-outline" size="large" />
|
||||
</a>
|
||||
</div>
|
||||
@if (
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToCreateAccount &&
|
||||
!user.settings.isRestrictedView
|
||||
) {
|
||||
<div class="fab-container">
|
||||
<a
|
||||
class="align-items-center d-flex justify-content-center"
|
||||
color="primary"
|
||||
mat-fab
|
||||
[queryParams]="{ createDialog: true }"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<ion-icon name="add-outline" size="large" />
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -10,16 +10,20 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>From</mat-label>
|
||||
<mat-select formControlName="fromAccount">
|
||||
<mat-option *ngFor="let account of accounts" [value]="account.id">
|
||||
<div class="d-flex">
|
||||
<gf-asset-profile-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
@for (account of accounts; track account) {
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
@if (account.Platform?.url) {
|
||||
<gf-asset-profile-icon
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/>
|
||||
}
|
||||
<span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -27,16 +31,20 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>To</mat-label>
|
||||
<mat-select formControlName="toAccount">
|
||||
<mat-option *ngFor="let account of accounts" [value]="account.id">
|
||||
<div class="d-flex">
|
||||
<gf-asset-profile-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
@for (account of accounts; track account) {
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
@if (account.Platform?.url) {
|
||||
<gf-asset-profile-icon
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/>
|
||||
}
|
||||
<span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
@ -2,14 +2,12 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
import { InfoItem } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
imports: [CommonModule],
|
||||
selector: 'gf-demo-page',
|
||||
standalone: true,
|
||||
templateUrl: './demo-page.html'
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
@ -125,12 +125,13 @@
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>,
|
||||
>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
,
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
>
|
||||
}
|
||||
or
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
@ -154,12 +155,13 @@
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>, send an e-mail to
|
||||
>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
, send an e-mail to
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
>
|
||||
}
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
|
@ -98,23 +98,21 @@
|
||||
start a new subscription.</mat-card-content
|
||||
>
|
||||
</mat-card>
|
||||
<mat-card
|
||||
*ngIf="user?.subscription?.type === 'Premium'"
|
||||
appearance="outlined"
|
||||
class="mb-3"
|
||||
>
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>I cannot find my broker in the list of platforms. What can I
|
||||
do?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
Please send an e-mail with the web address of your broker to
|
||||
<a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a> and we are
|
||||
happy to add it.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title
|
||||
>I cannot find my broker in the list of platforms. What can I
|
||||
do?</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
Please send an e-mail with the web address of your broker to
|
||||
<a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a> and we are
|
||||
happy to add it.
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Which devices are supported?</mat-card-title>
|
||||
@ -156,12 +154,13 @@
|
||||
href="https://twitter.com/ghostfolio_"
|
||||
title="Post to Ghostfolio on X (formerly Twitter)"
|
||||
>@ghostfolio_</a
|
||||
><ng-container *ngIf="user?.subscription?.type === 'Premium'"
|
||||
>, send an e-mail to
|
||||
>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
, send an e-mail to
|
||||
<a href="mailto:hi@ghostfol.io" title="Send an e-mail"
|
||||
>hi@ghostfol.io</a
|
||||
></ng-container
|
||||
>
|
||||
>
|
||||
}
|
||||
or start a discussion at
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
|
@ -4,7 +4,6 @@ import { InfoItem, User } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
@ -14,7 +13,6 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfPremiumIndicatorComponent,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
imports: [CommonModule],
|
||||
selector: 'gf-i18n-page',
|
||||
standalone: true,
|
||||
styleUrls: ['./i18n-page.scss'],
|
||||
|
@ -32,7 +32,7 @@
|
||||
<div class="container">
|
||||
<div class="button-container mb-5 row">
|
||||
<div class="align-items-center col d-flex justify-content-center">
|
||||
<ng-container *ngIf="hasPermissionToCreateUser">
|
||||
@if (hasPermissionToCreateUser) {
|
||||
<a
|
||||
color="primary"
|
||||
i18n
|
||||
@ -41,72 +41,74 @@
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="hasPermissionForDemo">
|
||||
<div *ngIf="hasPermissionToCreateUser" class="mx-3 text-muted" i18n>
|
||||
or
|
||||
</div>
|
||||
}
|
||||
@if (hasPermissionForDemo) {
|
||||
@if (hasPermissionToCreateUser) {
|
||||
<div class="mx-3 text-muted" i18n>or</div>
|
||||
}
|
||||
<a i18n mat-stroked-button [routerLink]="['/demo']">Live Demo</a>
|
||||
</ng-container>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasPermissionForStatistics" class="row mb-5">
|
||||
<div
|
||||
class="col-md-4 d-flex my-1"
|
||||
[ngClass]="{ 'justify-content-center': deviceType !== 'mobile' }"
|
||||
>
|
||||
<a
|
||||
class="d-block"
|
||||
title="Ghostfolio in Numbers: Monthly Active Users (MAU)"
|
||||
[routerLink]="['/open']"
|
||||
@if (hasPermissionForStatistics) {
|
||||
<div class="row mb-5">
|
||||
<div
|
||||
class="col-md-4 d-flex my-1"
|
||||
[ngClass]="{ 'justify-content-center': deviceType !== 'mobile' }"
|
||||
>
|
||||
<gf-value
|
||||
i18n
|
||||
icon="people-outline"
|
||||
size="large"
|
||||
[value]="statistics?.activeUsers30d ?? '-'"
|
||||
>Monthly Active Users</gf-value
|
||||
<a
|
||||
class="d-block"
|
||||
title="Ghostfolio in Numbers: Monthly Active Users (MAU)"
|
||||
[routerLink]="['/open']"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="col-md-4 d-flex my-1"
|
||||
[ngClass]="{ 'justify-content-center': deviceType !== 'mobile' }"
|
||||
>
|
||||
<a
|
||||
class="d-block"
|
||||
title="Ghostfolio in Numbers: Stars on GitHub"
|
||||
[routerLink]="['/open']"
|
||||
<gf-value
|
||||
i18n
|
||||
icon="people-outline"
|
||||
size="large"
|
||||
[value]="statistics?.activeUsers30d ?? '-'"
|
||||
>Monthly Active Users</gf-value
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="col-md-4 d-flex my-1"
|
||||
[ngClass]="{ 'justify-content-center': deviceType !== 'mobile' }"
|
||||
>
|
||||
<gf-value
|
||||
i18n
|
||||
icon="star-outline"
|
||||
size="large"
|
||||
[value]="statistics?.gitHubStargazers ?? '-'"
|
||||
>Stars on GitHub</gf-value
|
||||
<a
|
||||
class="d-block"
|
||||
title="Ghostfolio in Numbers: Stars on GitHub"
|
||||
[routerLink]="['/open']"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="col-md-4 d-flex my-1"
|
||||
[ngClass]="{ 'justify-content-center': deviceType !== 'mobile' }"
|
||||
>
|
||||
<a
|
||||
class="d-block"
|
||||
title="Ghostfolio in Numbers: Pulls on Docker Hub"
|
||||
[routerLink]="['/open']"
|
||||
<gf-value
|
||||
i18n
|
||||
icon="star-outline"
|
||||
size="large"
|
||||
[value]="statistics?.gitHubStargazers ?? '-'"
|
||||
>Stars on GitHub</gf-value
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="col-md-4 d-flex my-1"
|
||||
[ngClass]="{ 'justify-content-center': deviceType !== 'mobile' }"
|
||||
>
|
||||
<gf-value
|
||||
i18n
|
||||
icon="cloud-download-outline"
|
||||
size="large"
|
||||
[value]="statistics?.dockerHubPulls ?? '-'"
|
||||
>Pulls on Docker Hub</gf-value
|
||||
<a
|
||||
class="d-block"
|
||||
title="Ghostfolio in Numbers: Pulls on Docker Hub"
|
||||
[routerLink]="['/open']"
|
||||
>
|
||||
</a>
|
||||
<gf-value
|
||||
i18n
|
||||
icon="cloud-download-outline"
|
||||
size="large"
|
||||
[value]="statistics?.dockerHubPulls ?? '-'"
|
||||
>Pulls on Docker Hub</gf-value
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row mb-5">
|
||||
<div class="col-12 text-center text-muted">
|
||||
@ -320,112 +322,130 @@
|
||||
</div>
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<gf-carousel [aria-label]="'Testimonials'">
|
||||
<div *ngFor="let testimonial of testimonials" gf-carousel-item>
|
||||
<div class="d-flex px-4">
|
||||
<gf-logo class="mr-3 mt-2 pt-1" size="medium" [showLabel]="false" />
|
||||
<div>
|
||||
<div>{{ testimonial.quote }}</div>
|
||||
<div class="mt-2 text-muted">
|
||||
—
|
||||
<a
|
||||
*ngIf="testimonial.url"
|
||||
target="_blank"
|
||||
[href]="testimonial.url"
|
||||
>{{ testimonial.author }}</a
|
||||
>
|
||||
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span
|
||||
>,
|
||||
{{ testimonial.country }}
|
||||
@for (testimonial of testimonials; track testimonial) {
|
||||
<div gf-carousel-item>
|
||||
<div class="d-flex px-4">
|
||||
<gf-logo
|
||||
class="mr-3 mt-2 pt-1"
|
||||
size="medium"
|
||||
[showLabel]="false"
|
||||
/>
|
||||
<div>
|
||||
<div>{{ testimonial.quote }}</div>
|
||||
<div class="mt-2 text-muted">
|
||||
—
|
||||
@if (testimonial.url) {
|
||||
<a target="_blank" [href]="testimonial.url">{{
|
||||
testimonial.author
|
||||
}}</a>
|
||||
} @else {
|
||||
<span>{{ testimonial.author }}</span>
|
||||
}
|
||||
,
|
||||
{{ testimonial.country }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</gf-carousel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasPermissionForSubscription" class="row my-5">
|
||||
<div class="col-12">
|
||||
<h2 class="h4 text-center" i18n>
|
||||
Members from around the globe are using
|
||||
<a href="pricing"><strong>Ghostfolio Premium</strong></a>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-md-8 customer-map-container offset-md-2">
|
||||
<gf-world-map-chart format="👻" [countries]="countriesOfSubscribersMap" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasPermissionForSubscription" class="row my-3">
|
||||
<div class="col-12">
|
||||
<h2 class="h4 mb-1 text-center" i18n>
|
||||
How does <strong>Ghostfolio</strong> work?
|
||||
</h2>
|
||||
<p class="lead mb-3 text-center" i18n>Get started in only 3 steps</p>
|
||||
</div>
|
||||
<div class="col-md-4 my-2">
|
||||
<mat-card appearance="outlined" class="h-100">
|
||||
<mat-card-content class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-weight-bold" i18n>Sign up anonymously*</div>
|
||||
<div class="text-muted" i18n>
|
||||
<small>* no e-mail address nor credit card required</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2 text-muted text-right">1</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="col-md-4 my-2">
|
||||
<mat-card appearance="outlined" class="h-100">
|
||||
<mat-card-content class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-weight-bold" i18n>
|
||||
Add any of your historical transactions
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2 text-muted text-right">2</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="col-md-4 my-2">
|
||||
<mat-card appearance="outlined" class="h-100">
|
||||
<mat-card-content class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-weight-bold" i18n>
|
||||
Get valuable insights of your portfolio composition
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2 text-muted text-right">3</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasPermissionToCreateUser" class="row my-5">
|
||||
<div class="col">
|
||||
<h2 class="h4 mb-1 text-center" i18n>Are <strong>you</strong> ready?</h2>
|
||||
<p class="lead mb-3 text-center" i18n>
|
||||
Join now<ng-container *ngIf="hasPermissionForDemo">
|
||||
or check out the example account</ng-container
|
||||
>
|
||||
</p>
|
||||
<div class="align-items-center d-flex justify-content-center py-2">
|
||||
<a
|
||||
color="primary"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[routerLink]="routerLinkRegister"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
<ng-container *ngIf="hasPermissionForDemo">
|
||||
<div class="mx-3 text-muted" i18n>or</div>
|
||||
<a i18n mat-stroked-button [routerLink]="['/demo']">Live Demo</a>
|
||||
</ng-container>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="row my-5">
|
||||
<div class="col-12">
|
||||
<h2 class="h4 text-center" i18n>
|
||||
Members from around the globe are using
|
||||
<a href="pricing"><strong>Ghostfolio Premium</strong></a>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-md-8 customer-map-container offset-md-2">
|
||||
<gf-world-map-chart
|
||||
format="👻"
|
||||
[countries]="countriesOfSubscribersMap"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="row my-3">
|
||||
<div class="col-12">
|
||||
<h2 class="h4 mb-1 text-center" i18n>
|
||||
How does <strong>Ghostfolio</strong> work?
|
||||
</h2>
|
||||
<p class="lead mb-3 text-center" i18n>Get started in only 3 steps</p>
|
||||
</div>
|
||||
<div class="col-md-4 my-2">
|
||||
<mat-card appearance="outlined" class="h-100">
|
||||
<mat-card-content class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-weight-bold" i18n>Sign up anonymously*</div>
|
||||
<div class="text-muted" i18n>
|
||||
<small>* no e-mail address nor credit card required</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2 text-muted text-right">1</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="col-md-4 my-2">
|
||||
<mat-card appearance="outlined" class="h-100">
|
||||
<mat-card-content class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-weight-bold" i18n>
|
||||
Add any of your historical transactions
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2 text-muted text-right">2</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="col-md-4 my-2">
|
||||
<mat-card appearance="outlined" class="h-100">
|
||||
<mat-card-content class="d-flex flex-row">
|
||||
<div class="flex-grow-1">
|
||||
<div class="font-weight-bold" i18n>
|
||||
Get valuable insights of your portfolio composition
|
||||
</div>
|
||||
</div>
|
||||
<div class="pl-2 text-muted text-right">3</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (hasPermissionToCreateUser) {
|
||||
<div class="row my-5">
|
||||
<div class="col">
|
||||
<h2 class="h4 mb-1 text-center" i18n>
|
||||
Are <strong>you</strong> ready?
|
||||
</h2>
|
||||
<p class="lead mb-3 text-center" i18n>
|
||||
Join now
|
||||
@if (hasPermissionForDemo) {
|
||||
or check out the example account
|
||||
}
|
||||
</p>
|
||||
<div class="align-items-center d-flex justify-content-center py-2">
|
||||
<a
|
||||
color="primary"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[routerLink]="routerLinkRegister"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
@if (hasPermissionForDemo) {
|
||||
<div class="mx-3 text-muted" i18n>or</div>
|
||||
<a i18n mat-stroked-button [routerLink]="['/demo']">Live Demo</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
@ -4,8 +4,11 @@
|
||||
(keyup.enter)="activityForm.valid && onSubmit()"
|
||||
(ngSubmit)="onSubmit()"
|
||||
>
|
||||
<h1 *ngIf="data.activity.id" i18n mat-dialog-title>Update activity</h1>
|
||||
<h1 *ngIf="!data.activity.id" i18n mat-dialog-title>Add activity</h1>
|
||||
@if (data.activity.id) {
|
||||
<h1 i18n mat-dialog-title>Update activity</h1>
|
||||
} @else {
|
||||
<h1 i18n mat-dialog-title>Add activity</h1>
|
||||
}
|
||||
<div class="flex-grow-1 py-3" mat-dialog-content>
|
||||
<div class="mb-3">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
@ -81,25 +84,25 @@
|
||||
>
|
||||
<mat-label i18n>Account</mat-label>
|
||||
<mat-select formControlName="accountId">
|
||||
<mat-option
|
||||
*ngIf="
|
||||
!activityForm.get('accountId').hasValidator(Validators.required)
|
||||
"
|
||||
[value]="null"
|
||||
/>
|
||||
<mat-option
|
||||
*ngFor="let account of data.accounts"
|
||||
[value]="account.id"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<gf-asset-profile-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
@if (
|
||||
!activityForm.get('accountId').hasValidator(Validators.required)
|
||||
) {
|
||||
<mat-option [value]="null" />
|
||||
}
|
||||
@for (account of data.accounts; track account) {
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
@if (account.Platform?.url) {
|
||||
<gf-asset-profile-icon
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/>
|
||||
}
|
||||
<span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -139,9 +142,9 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Currency</mat-label>
|
||||
<mat-select formControlName="currency">
|
||||
<mat-option *ngFor="let currency of currencies" [value]="currency">{{
|
||||
currency
|
||||
}}</mat-option>
|
||||
@for (currency of currencies; track currency) {
|
||||
<mat-option [value]="currency">{{ currency }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -186,18 +189,24 @@
|
||||
>
|
||||
<div class="align-items-start d-flex">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label
|
||||
><ng-container [ngSwitch]="activityForm.get('type')?.value">
|
||||
<ng-container *ngSwitchCase="'DIVIDEND'" i18n
|
||||
>Dividend</ng-container
|
||||
>
|
||||
<ng-container *ngSwitchCase="'INTEREST'" i18n>Value</ng-container>
|
||||
<ng-container *ngSwitchCase="'ITEM'" i18n>Value</ng-container>
|
||||
<ng-container *ngSwitchCase="'LIABILITY'" i18n
|
||||
>Value</ng-container
|
||||
>
|
||||
<ng-container *ngSwitchDefault i18n>Unit Price</ng-container>
|
||||
</ng-container>
|
||||
<mat-label>
|
||||
@switch (activityForm.get('type')?.value) {
|
||||
@case ('DIVIDEND') {
|
||||
<ng-container i18n>Dividend</ng-container>
|
||||
}
|
||||
@case ('INTEREST') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@case ('ITEM') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@case ('LIABILITY') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@default {
|
||||
<ng-container i18n>Unit Price</ng-container>
|
||||
}
|
||||
}
|
||||
</mat-label>
|
||||
<input
|
||||
formControlName="unitPriceInCustomCurrency"
|
||||
@ -210,56 +219,67 @@
|
||||
[ngClass]="{ 'd-none': !activityForm.get('currency')?.value }"
|
||||
>
|
||||
<mat-select formControlName="currencyOfUnitPrice">
|
||||
<mat-option
|
||||
*ngFor="let currency of currencies"
|
||||
[value]="currency"
|
||||
>
|
||||
{{ currency }}
|
||||
</mat-option>
|
||||
@for (currency of currencies; track currency) {
|
||||
<mat-option [value]="currency">
|
||||
{{ currency }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</div>
|
||||
<mat-error
|
||||
*ngIf="
|
||||
activityForm.get('unitPriceInCustomCurrency').hasError('invalid')
|
||||
"
|
||||
><ng-container i18n
|
||||
>Oops! Could not get the historical exchange rate
|
||||
from</ng-container
|
||||
@if (
|
||||
activityForm.get('unitPriceInCustomCurrency').hasError('invalid')
|
||||
) {
|
||||
<mat-error
|
||||
><ng-container i18n
|
||||
>Oops! Could not get the historical exchange rate
|
||||
from</ng-container
|
||||
>
|
||||
{{
|
||||
activityForm.get('date')?.value | date: defaultDateFormat
|
||||
}}</mat-error
|
||||
>
|
||||
{{
|
||||
activityForm.get('date')?.value | date: defaultDateFormat
|
||||
}}</mat-error
|
||||
>
|
||||
}
|
||||
</mat-form-field>
|
||||
<button
|
||||
*ngIf="
|
||||
currentMarketPrice &&
|
||||
(data.activity.type === 'BUY' || data.activity.type === 'SELL') &&
|
||||
isToday(activityForm.get('date')?.value)
|
||||
"
|
||||
class="ml-2 mt-1 no-min-width"
|
||||
mat-button
|
||||
title="Apply current market price"
|
||||
type="button"
|
||||
(click)="applyCurrentMarketPrice()"
|
||||
>
|
||||
<ion-icon class="text-muted" name="refresh-outline" />
|
||||
</button>
|
||||
@if (
|
||||
currentMarketPrice &&
|
||||
(data.activity.type === 'BUY' || data.activity.type === 'SELL') &&
|
||||
isToday(activityForm.get('date')?.value)
|
||||
) {
|
||||
<button
|
||||
class="ml-2 mt-1 no-min-width"
|
||||
mat-button
|
||||
title="Apply current market price"
|
||||
type="button"
|
||||
(click)="applyCurrentMarketPrice()"
|
||||
>
|
||||
<ion-icon class="text-muted" name="refresh-outline" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label
|
||||
><ng-container [ngSwitch]="activityForm.get('type')?.value">
|
||||
<ng-container *ngSwitchCase="'DIVIDEND'" i18n
|
||||
>Dividend</ng-container
|
||||
>
|
||||
<ng-container *ngSwitchCase="'FEE'" i18n>Value</ng-container>
|
||||
<ng-container *ngSwitchCase="'INTEREST'" i18n>Value</ng-container>
|
||||
<ng-container *ngSwitchCase="'ITEM'" i18n>Value</ng-container>
|
||||
<ng-container *ngSwitchCase="'LIABILITY'" i18n>Value</ng-container>
|
||||
<ng-container *ngSwitchDefault i18n>Unit Price</ng-container>
|
||||
</ng-container>
|
||||
<mat-label>
|
||||
@switch (activityForm.get('type')?.value) {
|
||||
@case ('DIVIDEND') {
|
||||
<ng-container i18n>Dividend</ng-container>
|
||||
}
|
||||
@case ('FEE') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@case ('INTEREST') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@case ('ITEM') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@case ('LIABILITY') {
|
||||
<ng-container i18n>Value</ng-container>
|
||||
}
|
||||
@default {
|
||||
<ng-container i18n>Unit Price</ng-container>
|
||||
}
|
||||
}
|
||||
</mat-label>
|
||||
<input formControlName="unitPrice" matInput type="number" />
|
||||
<span class="ml-2" matTextSuffix>{{
|
||||
@ -286,15 +306,17 @@
|
||||
>
|
||||
{{ activityForm.get('currencyOfUnitPrice').value }}
|
||||
</div>
|
||||
<mat-error
|
||||
*ngIf="activityForm.get('feeInCustomCurrency').hasError('invalid')"
|
||||
><ng-container i18n
|
||||
>Oops! Could not get the historical exchange rate from</ng-container
|
||||
@if (activityForm.get('feeInCustomCurrency').hasError('invalid')) {
|
||||
<mat-error
|
||||
><ng-container i18n
|
||||
>Oops! Could not get the historical exchange rate
|
||||
from</ng-container
|
||||
>
|
||||
{{
|
||||
activityForm.get('date')?.value | date: defaultDateFormat
|
||||
}}</mat-error
|
||||
>
|
||||
{{
|
||||
activityForm.get('date')?.value | date: defaultDateFormat
|
||||
}}</mat-error
|
||||
>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="d-none">
|
||||
@ -326,11 +348,11 @@
|
||||
<mat-label i18n>Asset Class</mat-label>
|
||||
<mat-select formControlName="assetClass">
|
||||
<mat-option [value]="null" />
|
||||
<mat-option
|
||||
*ngFor="let assetClass of assetClasses"
|
||||
[value]="assetClass.id"
|
||||
>{{ assetClass.label }}</mat-option
|
||||
>
|
||||
@for (assetClass of assetClasses; track assetClass) {
|
||||
<mat-option [value]="assetClass.id">{{
|
||||
assetClass.label
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -342,11 +364,11 @@
|
||||
<mat-label i18n>Asset Sub Class</mat-label>
|
||||
<mat-select formControlName="assetSubClass">
|
||||
<mat-option [value]="null" />
|
||||
<mat-option
|
||||
*ngFor="let assetSubClass of assetSubClasses"
|
||||
[value]="assetSubClass.id"
|
||||
>{{ assetSubClass.label }}</mat-option
|
||||
>
|
||||
@for (assetSubClass of assetSubClasses; track assetSubClass) {
|
||||
<mat-option [value]="assetSubClass.id">{{
|
||||
assetSubClass.label
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -354,15 +376,16 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Tags</mat-label>
|
||||
<mat-chip-grid #tagsChipList>
|
||||
<mat-chip-row
|
||||
*ngFor="let tag of activityForm.get('tags')?.value"
|
||||
matChipRemove
|
||||
[removable]="true"
|
||||
(removed)="onRemoveTag(tag)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
<ion-icon class="ml-2" matPrefix name="close-outline" />
|
||||
</mat-chip-row>
|
||||
@for (tag of activityForm.get('tags')?.value; track tag) {
|
||||
<mat-chip-row
|
||||
matChipRemove
|
||||
[removable]="true"
|
||||
(removed)="onRemoveTag(tag)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
<ion-icon class="ml-2" matPrefix name="close-outline" />
|
||||
</mat-chip-row>
|
||||
}
|
||||
<input
|
||||
#tagInput
|
||||
name="close-outline"
|
||||
@ -375,12 +398,11 @@
|
||||
#autocompleteTags="matAutocomplete"
|
||||
(optionSelected)="onAddTag($event)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let tag of filteredTagsObservable | async"
|
||||
[value]="tag.id"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</mat-option>
|
||||
@for (tag of filteredTagsObservable | async; track tag) {
|
||||
<mat-option [value]="tag.id">
|
||||
{{ tag.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
@ -16,12 +16,11 @@
|
||||
>
|
||||
<mat-step [completed]="importStep === 0" [selected]="importStep === 0">
|
||||
<ng-template matStepLabel>
|
||||
<ng-container *ngIf="mode === 'DIVIDEND'" i18n
|
||||
>Select Holding</ng-container
|
||||
>
|
||||
<ng-container *ngIf="mode !== 'DIVIDEND'" i18n
|
||||
>Select File</ng-container
|
||||
>
|
||||
@if (mode === 'DIVIDEND') {
|
||||
<ng-container i18n>Select Holding</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>Select File</ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
<div class="pt-3">
|
||||
@if (mode === 'DIVIDEND') {
|
||||
@ -35,30 +34,29 @@
|
||||
<mat-select-trigger>{{
|
||||
uniqueAssetForm.get('uniqueAsset')?.value?.name
|
||||
}}</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let holding of holdings"
|
||||
class="line-height-1"
|
||||
[value]="{
|
||||
dataSource: holding.dataSource,
|
||||
name: holding.name,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
>
|
||||
<span
|
||||
><b>{{ holding.name }}</b></span
|
||||
@for (holding of holdings; track holding) {
|
||||
<mat-option
|
||||
class="line-height-1"
|
||||
[value]="{
|
||||
dataSource: holding.dataSource,
|
||||
name: holding.name,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ holding.symbol | gfSymbol }} ·
|
||||
{{ holding.currency }}</small
|
||||
>
|
||||
</mat-option>
|
||||
<span
|
||||
><b>{{ holding.name }}</b></span
|
||||
>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ holding.symbol | gfSymbol }} ·
|
||||
{{ holding.currency }}</small
|
||||
>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-spinner
|
||||
*ngIf="isLoading"
|
||||
class="position-absolute"
|
||||
[diameter]="20"
|
||||
/>
|
||||
@if (isLoading) {
|
||||
<mat-spinner class="position-absolute" [diameter]="20" />
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
@ -111,36 +109,36 @@
|
||||
|
||||
<mat-step [completed]="importStep === 1" [selected]="importStep === 1">
|
||||
<ng-template matStepLabel>
|
||||
<ng-container *ngIf="mode === 'DIVIDEND'" i18n
|
||||
>Select Dividends</ng-container
|
||||
>
|
||||
<ng-container *ngIf="mode !== 'DIVIDEND'" i18n
|
||||
>Select Activities</ng-container
|
||||
>
|
||||
@if (mode === 'DIVIDEND') {
|
||||
<ng-container i18n>Select Dividends</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>Select Activities</ng-container>
|
||||
}
|
||||
</ng-template>
|
||||
<div class="pt-3">
|
||||
@if (errorMessages?.length === 0) {
|
||||
<gf-activities-table
|
||||
*ngIf="importStep === 1"
|
||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data?.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToDeleteActivity]="false"
|
||||
[hasPermissionToExportActivities]="false"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data?.user?.settings?.locale"
|
||||
[pageSize]="maxSafeInteger"
|
||||
[showActions]="false"
|
||||
[showCheckbox]="true"
|
||||
[showSymbolColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(selectedActivities)="updateSelection($event)"
|
||||
/>
|
||||
@if (importStep === 1) {
|
||||
<gf-activities-table
|
||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data?.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToDeleteActivity]="false"
|
||||
[hasPermissionToExportActivities]="false"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data?.user?.settings?.locale"
|
||||
[pageSize]="maxSafeInteger"
|
||||
[showActions]="false"
|
||||
[showCheckbox]="true"
|
||||
[showSymbolColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(selectedActivities)="updateSelection($event)"
|
||||
/>
|
||||
}
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
@ -157,25 +155,23 @@
|
||||
</div>
|
||||
} @else {
|
||||
<mat-accordion displayMode="flat">
|
||||
<mat-expansion-panel
|
||||
*ngFor="let message of errorMessages; let i = index"
|
||||
[disabled]="!details[i]"
|
||||
>
|
||||
<mat-expansion-panel-header class="pl-1">
|
||||
<mat-panel-title>
|
||||
<div class="d-flex">
|
||||
<div class="align-items-center d-flex mr-2">
|
||||
<ion-icon name="warning-outline" />
|
||||
@for (message of errorMessages; track message; let i = $index) {
|
||||
<mat-expansion-panel [disabled]="!details[i]">
|
||||
<mat-expansion-panel-header class="pl-1">
|
||||
<mat-panel-title>
|
||||
<div class="d-flex">
|
||||
<div class="align-items-center d-flex mr-2">
|
||||
<ion-icon name="warning-outline" />
|
||||
</div>
|
||||
<div>{{ message }}</div>
|
||||
</div>
|
||||
<div>{{ message }}</div>
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<pre
|
||||
*ngIf="details[i]"
|
||||
class="m-0"
|
||||
><code>{{ details[i] | json }}</code></pre>
|
||||
</mat-expansion-panel>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
@if (details[i]) {
|
||||
<pre class="m-0"><code>{{ details[i] | json }}</code></pre>
|
||||
}
|
||||
</mat-expansion-panel>
|
||||
}
|
||||
</mat-accordion>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
|
@ -27,10 +27,9 @@
|
||||
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
|
||||
>
|
||||
<span i18n>Absolute Asset Performance</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
@ -71,10 +70,9 @@
|
||||
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
|
||||
>
|
||||
<span i18n>Absolute Currency Performance</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
@ -170,39 +168,42 @@
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<ol class="mb-0 ml-1 pl-3">
|
||||
<li *ngFor="let holding of top3" class="py-1">
|
||||
<a
|
||||
class="d-flex"
|
||||
[queryParams]="{
|
||||
dataSource: holding.dataSource,
|
||||
holdingDetailDialog: true,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<div class="flex-grow-1 mr-2">{{ holding.name }}</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="holding.netPerformancePercentWithCurrencyEffect"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
@for (holding of top3; track holding) {
|
||||
<li class="py-1">
|
||||
<a
|
||||
class="d-flex"
|
||||
[queryParams]="{
|
||||
dataSource: holding.dataSource,
|
||||
holdingDetailDialog: true,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<div class="flex-grow-1 mr-2">{{ holding.name }}</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="holding.netPerformancePercentWithCurrencyEffect"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ol>
|
||||
<div>
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="!top3"
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (!top3) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@ -216,39 +217,42 @@
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<ol class="mb-0 ml-1 pl-3">
|
||||
<li *ngFor="let holding of bottom3" class="py-1">
|
||||
<a
|
||||
class="d-flex"
|
||||
[queryParams]="{
|
||||
dataSource: holding.dataSource,
|
||||
holdingDetailDialog: true,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<div class="flex-grow-1 mr-2">{{ holding.name }}</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="holding.netPerformancePercentWithCurrencyEffect"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
@for (holding of bottom3; track holding) {
|
||||
<li class="py-1">
|
||||
<a
|
||||
class="d-flex"
|
||||
[queryParams]="{
|
||||
dataSource: holding.dataSource,
|
||||
holdingDetailDialog: true,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<div class="flex-grow-1 mr-2">{{ holding.name }}</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="holding.netPerformancePercentWithCurrencyEffect"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ol>
|
||||
<div>
|
||||
<ngx-skeleton-loader
|
||||
*ngIf="!bottom3"
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
@if (!bottom3) {
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@ -262,10 +266,9 @@
|
||||
class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"
|
||||
>
|
||||
<span i18n>Portfolio Evolution</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-container">
|
||||
@ -292,10 +295,9 @@
|
||||
class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"
|
||||
>
|
||||
<span i18n>Investment Timeline</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</div>
|
||||
<gf-toggle
|
||||
class="d-none d-lg-block"
|
||||
@ -305,26 +307,28 @@
|
||||
(change)="onChangeGroupBy($event.value)"
|
||||
/>
|
||||
</div>
|
||||
<div *ngIf="streaks" class="row">
|
||||
<div class="col-md-6 col-xs-12 my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[unit]="unitCurrentStreak"
|
||||
[value]="streaks?.currentStreak"
|
||||
>Current Streak</gf-value
|
||||
>
|
||||
@if (streaks) {
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-xs-12 my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[unit]="unitCurrentStreak"
|
||||
[value]="streaks?.currentStreak"
|
||||
>Current Streak</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-6 col-xs-12 my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[unit]="unitLongestStreak"
|
||||
[value]="streaks?.longestStreak"
|
||||
>Longest Streak</gf-value
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-xs-12 my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[unit]="unitLongestStreak"
|
||||
[value]="streaks?.longestStreak"
|
||||
>Longest Streak</gf-value
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="chart-container">
|
||||
<gf-investment-chart
|
||||
class="h-100"
|
||||
@ -350,10 +354,9 @@
|
||||
class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"
|
||||
>
|
||||
<span i18n>Dividend Timeline</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</div>
|
||||
<gf-toggle
|
||||
class="d-none d-lg-block"
|
||||
|
@ -4,11 +4,10 @@
|
||||
<h2 class="d-none d-sm-block h3 mb-3 text-center" i18n>FIRE</h2>
|
||||
<div>
|
||||
<h4 class="align-items-center d-flex mb-3">
|
||||
<span i18n>Calculator</span
|
||||
><gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
<span i18n>Calculator</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<gf-fire-calculator
|
||||
[annualInterestRate]="user?.settings?.annualInterestRate"
|
||||
@ -38,67 +37,68 @@
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="align-items-center d-flex">
|
||||
<span i18n>4% Rule</span
|
||||
><gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
<span i18n>4% Rule</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<div *ngIf="isLoading">
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="my-1"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '10rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="!isLoading"
|
||||
i18n
|
||||
[ngClass]="{ 'text-muted': user?.subscription?.type === 'Basic' }"
|
||||
>
|
||||
If you retire today, you would be able to withdraw
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
class="d-inline-block"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="withdrawalRatePerYear?.toNumber()"
|
||||
@if (isLoading) {
|
||||
<div>
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
class="my-1"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
per year</span
|
||||
<ngx-skeleton-loader
|
||||
animation="pulse"
|
||||
[theme]="{
|
||||
height: '1rem',
|
||||
width: '10rem'
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
i18n
|
||||
[ngClass]="{ 'text-muted': user?.subscription?.type === 'Basic' }"
|
||||
>
|
||||
or
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
class="d-inline-block"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="withdrawalRatePerMonth?.toNumber()"
|
||||
/>
|
||||
per month</span
|
||||
>, based on your total assets of
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
class="d-inline-block"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="fireWealth?.toNumber()"
|
||||
/>
|
||||
</span>
|
||||
and a withdrawal rate of 4%.
|
||||
</div>
|
||||
If you retire today, you would be able to withdraw
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
class="d-inline-block"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="withdrawalRatePerYear?.toNumber()"
|
||||
/>
|
||||
per year</span
|
||||
>
|
||||
or
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
class="d-inline-block"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="withdrawalRatePerMonth?.toNumber()"
|
||||
/>
|
||||
per month</span
|
||||
>, based on your total assets of
|
||||
<span class="font-weight-bold"
|
||||
><gf-value
|
||||
class="d-inline-block"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="fireWealth?.toNumber()"
|
||||
/>
|
||||
</span>
|
||||
and a withdrawal rate of 4%.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -119,11 +119,10 @@
|
||||
</p>
|
||||
<div class="mb-4">
|
||||
<h4 class="align-items-center d-flex m-0">
|
||||
<span i18n>Emergency Fund</span
|
||||
><gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
<span i18n>Emergency Fund</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
@ -132,11 +131,10 @@
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<h4 class="align-items-center d-flex m-0">
|
||||
<span i18n>Currency Cluster Risks</span
|
||||
><gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
<span i18n>Currency Cluster Risks</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
@ -145,11 +143,10 @@
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<h4 class="align-items-center d-flex m-0">
|
||||
<span i18n>Account Cluster Risks</span
|
||||
><gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
<span i18n>Account Cluster Risks</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
@ -158,11 +155,10 @@
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="align-items-center d-flex m-0">
|
||||
<span i18n>Fees</span
|
||||
><gf-premium-indicator
|
||||
*ngIf="user?.subscription?.type === 'Basic'"
|
||||
class="ml-1"
|
||||
/>
|
||||
<span i18n>Fees</span>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<gf-rules
|
||||
[hasPermissionToCreateOrder]="hasPermissionToCreateOrder"
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
@ -9,18 +9,20 @@
|
||||
for most people. Revenue is used to cover the costs of the hosting
|
||||
infrastructure and to fund ongoing development.
|
||||
</p>
|
||||
<p *ngIf="user?.subscription?.type === 'Basic'">
|
||||
If you plan to open an account at <i>DEGIRO</i>, <i>finpension</i>,
|
||||
<i>frankly</i>, <i>Interactive Brokers</i>, <i>Swissquote</i>,
|
||||
<i>VIAC</i>, or <i>Zak</i>, please
|
||||
<a href="mailto:hi@ghostfol.io?Subject=Referral link for..."
|
||||
>contact us</a
|
||||
>
|
||||
to use our referral link and get a Ghostfolio Premium membership for
|
||||
one year. Looking for a student discount? Request it
|
||||
<a href="mailto:hi@ghostfol.io?Subject=Student Discount">here</a> with
|
||||
your university e-mail address.
|
||||
</p>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<p>
|
||||
If you plan to open an account at <i>DEGIRO</i>, <i>finpension</i>,
|
||||
<i>frankly</i>, <i>Interactive Brokers</i>, <i>Swissquote</i>,
|
||||
<i>VIAC</i>, or <i>Zak</i>, please
|
||||
<a href="mailto:hi@ghostfol.io?Subject=Referral link for..."
|
||||
>contact us</a
|
||||
>
|
||||
to use our referral link and get a Ghostfolio Premium membership for
|
||||
one year. Looking for a student discount? Request it
|
||||
<a href="mailto:hi@ghostfol.io?Subject=Student Discount">here</a>
|
||||
with your university e-mail address.
|
||||
</p>
|
||||
}
|
||||
<p i18n>
|
||||
If you prefer to run Ghostfolio on your own infrastructure, please
|
||||
find the source code and further instructions on
|
||||
@ -91,15 +93,14 @@
|
||||
</div>
|
||||
<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"
|
||||
>
|
||||
<a color="primary" mat-flat-button> </a>
|
||||
<p class="m-0 text-muted">
|
||||
<small> </small>
|
||||
</p>
|
||||
</div>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<div class="d-none d-lg-block hidden mt-3 text-center">
|
||||
<a color="primary" mat-flat-button> </a>
|
||||
<p class="m-0 text-muted">
|
||||
<small> </small>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@ -113,9 +114,11 @@
|
||||
<div class="flex-grow-1">
|
||||
<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" />
|
||||
</div>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<div>
|
||||
<ion-icon class="mr-1" name="checkmark-outline" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p i18n>
|
||||
For new investors who are just getting started with trading.
|
||||
@ -148,15 +151,14 @@
|
||||
</div>
|
||||
<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"
|
||||
>
|
||||
<a color="primary" mat-flat-button> </a>
|
||||
<p class="m-0 text-muted">
|
||||
<small> </small>
|
||||
</p>
|
||||
</div>
|
||||
@if (user?.subscription?.type === 'Basic') {
|
||||
<div class="d-none d-lg-block hidden mt-3 text-center">
|
||||
<a color="primary" mat-flat-button> </a>
|
||||
<p class="m-0 text-muted">
|
||||
<small> </small>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
@ -173,9 +175,11 @@
|
||||
<span>Premium</span>
|
||||
<gf-premium-indicator class="ml-1" [enableLink]="false" />
|
||||
</h4>
|
||||
<div *ngIf="user?.subscription?.type === 'Premium'">
|
||||
<ion-icon class="mr-1" name="checkmark-outline" />
|
||||
</div>
|
||||
@if (user?.subscription?.type === 'Premium') {
|
||||
<div>
|
||||
<ion-icon class="mr-1" name="checkmark-outline" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<p i18n>
|
||||
For ambitious investors who need the full picture of their
|
||||
@ -240,58 +244,61 @@
|
||||
<p i18n>Fully managed Ghostfolio cloud offering.</p>
|
||||
<p class="h5 text-right" [hidden]="!price">
|
||||
<span class="font-weight-normal">
|
||||
<ng-container *ngIf="coupon"
|
||||
><del class="text-muted"
|
||||
@if (coupon) {
|
||||
<del class="text-muted"
|
||||
>{{ baseCurrency }} {{ price }}</del
|
||||
> {{ baseCurrency }} <strong>{{
|
||||
price - coupon
|
||||
}}</strong>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!coupon"
|
||||
>{{ baseCurrency }} <strong>{{
|
||||
price
|
||||
}}</strong></ng-container
|
||||
> <span i18n>per year</span></span
|
||||
} @else {
|
||||
{{ baseCurrency }} <strong>{{ price }}</strong>
|
||||
}
|
||||
<span i18n>per year</span></span
|
||||
>
|
||||
</p>
|
||||
<div
|
||||
*ngIf="
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.subscription?.type === 'Basic'
|
||||
"
|
||||
class="mt-3 text-center"
|
||||
>
|
||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||
<ng-container
|
||||
*ngIf="user.subscription.offer === 'default'"
|
||||
i18n
|
||||
>Upgrade Plan</ng-container
|
||||
@if (
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.subscription?.type === 'Basic'
|
||||
) {
|
||||
<div class="mt-3 text-center">
|
||||
<button
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="onCheckout()"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="
|
||||
@if (user.subscription.offer === 'default') {
|
||||
<ng-container i18n>Upgrade Plan</ng-container>
|
||||
} @else if (
|
||||
user.subscription.offer === 'renewal' ||
|
||||
user.subscription.offer === 'renewal-early-bird'
|
||||
"
|
||||
i18n
|
||||
>Renew Plan</ng-container
|
||||
>
|
||||
</button>
|
||||
<p class="m-0 text-muted">
|
||||
<small i18n>One-time payment, no auto-renewal.</small>
|
||||
</p>
|
||||
</div>
|
||||
) {
|
||||
<ng-container i18n>Renew Plan</ng-container>
|
||||
}
|
||||
</button>
|
||||
<p class="m-0 text-muted">
|
||||
<small i18n>One-time payment, no auto-renewal.</small>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!user" class="row">
|
||||
<div class="col mt-3 text-center">
|
||||
<a color="primary" i18n mat-flat-button [routerLink]="routerLinkRegister">
|
||||
Get Started
|
||||
</a>
|
||||
<p class="m-0 text-muted"><small i18n>It’s free.</small></p>
|
||||
@if (!user) {
|
||||
<div class="row">
|
||||
<div class="col mt-3 text-center">
|
||||
<a
|
||||
color="primary"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[routerLink]="routerLinkRegister"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
<p class="m-0 text-muted"><small i18n>It’s free.</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -24,110 +24,121 @@
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div *ngIf="portfolioPublicDetails?.hasDetails" class="col-md-4">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n>Currencies</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
[isInPercent]="true"
|
||||
[keys]="['currency']"
|
||||
[maxItems]="10"
|
||||
[positions]="positions"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div *ngIf="portfolioPublicDetails?.hasDetails" class="col-md-4">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n>Sectors</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div *ngIf="portfolioPublicDetails?.hasDetails" class="col-md-4">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n>Continents</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[positions]="continents"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="portfolioPublicDetails?.hasDetails" class="row world-map-chart">
|
||||
<div class="col-lg">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n>Regions</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="world-map-chart-container">
|
||||
<gf-world-map-chart
|
||||
format="{0}%"
|
||||
[countries]="countries"
|
||||
[isInPercent]="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.developedMarkets?.value"
|
||||
>Developed Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.emergingMarkets?.value"
|
||||
>Emerging Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.otherMarkets?.value"
|
||||
>Other Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="markets?.[UNKNOWN_KEY]?.value > 0"
|
||||
class="col-xs-12 col-md my-2"
|
||||
@if (portfolioPublicDetails?.hasDetails) {
|
||||
<div class="col-md-4">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n
|
||||
>Currencies</mat-card-title
|
||||
>
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.[UNKNOWN_KEY]?.value"
|
||||
>No data available</gf-value
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
[isInPercent]="true"
|
||||
[keys]="['currency']"
|
||||
[maxItems]="10"
|
||||
[positions]="positions"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
@if (portfolioPublicDetails?.hasDetails) {
|
||||
<div class="col-md-4">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n>Sectors</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
@if (portfolioPublicDetails?.hasDetails) {
|
||||
<div class="col-md-4">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n
|
||||
>Continents</mat-card-title
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[positions]="continents"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (portfolioPublicDetails?.hasDetails) {
|
||||
<div class="row world-map-chart">
|
||||
<div class="col-lg">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-header class="overflow-hidden w-100">
|
||||
<mat-card-title class="text-truncate" i18n>Regions</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="world-map-chart-container">
|
||||
<gf-world-map-chart
|
||||
format="{0}%"
|
||||
[countries]="countries"
|
||||
[isInPercent]="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.developedMarkets?.value"
|
||||
>Developed Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.emergingMarkets?.value"
|
||||
>Emerging Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.otherMarkets?.value"
|
||||
>Other Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@if (markets?.[UNKNOWN_KEY]?.value > 0) {
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.[UNKNOWN_KEY]?.value"
|
||||
>No data available</gf-value
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<gf-holdings-table
|
||||
|
@ -14,44 +14,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasPermissionToCreateUser" class="button-container row">
|
||||
<div class="align-items-center col d-flex justify-content-center">
|
||||
<div class="py-5 text-center">
|
||||
<button
|
||||
class="d-inline-block"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="createAccount()"
|
||||
>
|
||||
<ng-container i18n>Create Account</ng-container>
|
||||
</button>
|
||||
<ng-container *ngIf="hasPermissionForSocialLogin">
|
||||
<div class="my-3 text-muted" i18n>or</div>
|
||||
@if (hasPermissionToCreateUser) {
|
||||
<div class="button-container row">
|
||||
<div class="align-items-center col d-flex justify-content-center">
|
||||
<div class="py-5 text-center">
|
||||
<button
|
||||
*ngIf="false"
|
||||
class="d-block mb-2 px-4 rounded-pill"
|
||||
mat-stroked-button
|
||||
(click)="onLoginWithInternetIdentity()"
|
||||
class="d-inline-block"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="createAccount()"
|
||||
>
|
||||
<img
|
||||
class="mr-2"
|
||||
src="../assets/icons/internet-computer.svg"
|
||||
style="height: 0.75rem"
|
||||
/>
|
||||
<span i18n>Continue with Internet Identity</span>
|
||||
<ng-container i18n>Create Account</ng-container>
|
||||
</button>
|
||||
<a
|
||||
class="px-4 rounded-pill w-100"
|
||||
href="../api/v1/auth/google"
|
||||
mat-stroked-button
|
||||
><img
|
||||
class="mr-2"
|
||||
src="../assets/icons/google.svg"
|
||||
style="height: 1rem"
|
||||
/><span i18n>Continue with Google</span></a
|
||||
>
|
||||
</ng-container>
|
||||
@if (hasPermissionForSocialLogin) {
|
||||
<div class="my-3 text-muted" i18n>or</div>
|
||||
@if (false) {
|
||||
<button
|
||||
class="d-block mb-2 px-4 rounded-pill"
|
||||
mat-stroked-button
|
||||
(click)="onLoginWithInternetIdentity()"
|
||||
>
|
||||
<img
|
||||
class="mr-2"
|
||||
src="../assets/icons/internet-computer.svg"
|
||||
style="height: 0.75rem"
|
||||
/>
|
||||
<span i18n>Continue with Internet Identity</span>
|
||||
</button>
|
||||
}
|
||||
<a
|
||||
class="px-4 rounded-pill w-100"
|
||||
href="../api/v1/auth/google"
|
||||
mat-stroked-button
|
||||
><img
|
||||
class="mr-2"
|
||||
src="../assets/icons/google.svg"
|
||||
style="height: 1rem"
|
||||
/><span i18n>Continue with Google</span></a
|
||||
>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span i18n>Create Account</span
|
||||
><span *ngIf="data.role === 'ADMIN'" class="badge badge-light ml-2">{{
|
||||
data.role
|
||||
}}</span>
|
||||
<span i18n>Create Account</span>
|
||||
@if (data.role === 'ADMIN') {
|
||||
<span class="badge badge-light ml-2">{{ data.role }}</span>
|
||||
}
|
||||
</h1>
|
||||
<div class="py-3" mat-dialog-content>
|
||||
<div>
|
||||
|
@ -88,16 +88,22 @@
|
||||
Available in
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container
|
||||
*ngFor="let language of product1.languages; last as isLast"
|
||||
>{{ language }}{{ isLast ? '' : ', ' }}</ng-container
|
||||
>
|
||||
@for (
|
||||
language of product1.languages;
|
||||
track language;
|
||||
let isLast = $last
|
||||
) {
|
||||
{{ language }}{{ isLast ? '' : ', ' }}
|
||||
}
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container
|
||||
*ngFor="let language of product2.languages; last as isLast"
|
||||
>{{ language }}{{ isLast ? '' : ', ' }}</ng-container
|
||||
>
|
||||
@for (
|
||||
language of product2.languages;
|
||||
track language;
|
||||
let isLast = $last
|
||||
) {
|
||||
{{ language }}{{ isLast ? '' : ', ' }}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="mat-mdc-row">
|
||||
@ -105,18 +111,18 @@
|
||||
Open Source Software
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product1.isOpenSource" i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container *ngIf="!product1.isOpenSource" i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product1.isOpenSource) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product2.isOpenSource" i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container *ngIf="!product2.isOpenSource" i18n
|
||||
>❌ No
|
||||
</ng-container>
|
||||
@if (product2.isOpenSource) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>❌ No </ng-container>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="mat-mdc-row">
|
||||
@ -124,26 +130,18 @@
|
||||
Self-Hosting
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container
|
||||
*ngIf="product1.hasSelfHostingAbility === true"
|
||||
i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container
|
||||
*ngIf="product1.hasSelfHostingAbility === false"
|
||||
i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product1.hasSelfHostingAbility === true) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else if (product1.hasSelfHostingAbility === false) {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container
|
||||
*ngIf="product2.hasSelfHostingAbility === true"
|
||||
i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container
|
||||
*ngIf="product2.hasSelfHostingAbility === false"
|
||||
i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product2.hasSelfHostingAbility === true) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else if (product2.hasSelfHostingAbility === false) {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="mat-mdc-row">
|
||||
@ -151,18 +149,18 @@
|
||||
Use anonymously
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product1.useAnonymously === true" i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container *ngIf="product1.useAnonymously === false" i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product1.useAnonymously === true) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else if (product1.useAnonymously === false) {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product2.useAnonymously === true" i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container *ngIf="product2.useAnonymously === false" i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product2.useAnonymously === true) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else if (product2.useAnonymously === false) {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="mat-mdc-row">
|
||||
@ -170,18 +168,18 @@
|
||||
Free Plan
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product1.hasFreePlan === true" i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container *ngIf="product1.hasFreePlan === false" i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product1.hasFreePlan === true) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else if (product1.hasFreePlan === false) {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product2.hasFreePlan === true" i18n
|
||||
>✅ Yes</ng-container
|
||||
><ng-container *ngIf="product2.hasFreePlan === false" i18n
|
||||
>❌ No</ng-container
|
||||
>
|
||||
@if (product2.hasFreePlan === true) {
|
||||
<ng-container i18n>✅ Yes</ng-container>
|
||||
} @else if (product2.hasFreePlan === false) {
|
||||
<ng-container i18n>❌ No</ng-container>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="mat-mdc-row">
|
||||
@ -191,18 +189,20 @@
|
||||
<span i18n>year</span>
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product2.pricingPerYear"
|
||||
><span i18n>Starting from</span>
|
||||
@if (product2.pricingPerYear) {
|
||||
<span i18n>Starting from</span>
|
||||
{{ product2.pricingPerYear }} /
|
||||
<span i18n>year</span></ng-container
|
||||
>
|
||||
<span i18n>year</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="product1.note || product2.note" class="mat-mdc-row">
|
||||
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Notes</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">{{ product1.note }}</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">{{ product2.note }}</td>
|
||||
</tr>
|
||||
@if (product1.note || product2.note) {
|
||||
<tr class="mat-mdc-row">
|
||||
<td class="mat-mdc-cell px-3 py-2 text-right" i18n>Notes</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">{{ product1.note }}</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">{{ product2.note }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
@ -2,7 +2,6 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.s
|
||||
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
|
||||
import { GfLogoComponent } from '@ghostfolio/ui/logo';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
@ -12,12 +11,7 @@ import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfLogoComponent,
|
||||
MatButtonModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
imports: [GfLogoComponent, MatButtonModule, MatProgressSpinnerModule],
|
||||
selector: 'gf-webauthn-page',
|
||||
standalone: true,
|
||||
styleUrls: ['./webauthn-page.scss'],
|
||||
|
@ -8,22 +8,23 @@
|
||||
[disablePagination]="true"
|
||||
[tabPanel]="tabPanel"
|
||||
>
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngIf="tab.showCondition !== false"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
</ng-container>
|
||||
@for (tab of tabs; track tab) {
|
||||
@if (tab.showCondition !== false) {
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
class="no-min-width px-3"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
[active]="rla.isActive"
|
||||
[routerLink]="tab.path"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
|
Loading…
x
Reference in New Issue
Block a user