+
Data Source
-
-
-
+
+
+
+
}
diff --git a/apps/client/src/app/core/paths.ts b/apps/client/src/app/core/paths.ts
index 801228bba..17ce75c7c 100644
--- a/apps/client/src/app/core/paths.ts
+++ b/apps/client/src/app/core/paths.ts
@@ -7,5 +7,6 @@ export const paths = {
pricing: $localize`pricing`,
privacyPolicy: $localize`privacy-policy`,
register: $localize`register`,
- resources: $localize`resources`
+ resources: $localize`resources`,
+ termsOfService: $localize`terms-of-service`
};
diff --git a/apps/client/src/app/pages/about/about-page-routing.module.ts b/apps/client/src/app/pages/about/about-page-routing.module.ts
index 060030930..a7312001f 100644
--- a/apps/client/src/app/pages/about/about-page-routing.module.ts
+++ b/apps/client/src/app/pages/about/about-page-routing.module.ts
@@ -44,6 +44,13 @@ const routes: Routes = [
import('./privacy-policy/privacy-policy-page.module').then(
(m) => m.PrivacyPolicyPageModule
)
+ },
+ {
+ path: paths.termsOfService,
+ loadChildren: () =>
+ import('./terms-of-service/terms-of-service-page.module').then(
+ (m) => m.TermsOfServicePageModule
+ )
}
],
component: AboutPageComponent,
diff --git a/apps/client/src/app/pages/about/about-page.component.ts b/apps/client/src/app/pages/about/about-page.component.ts
index 399cba238..46a080383 100644
--- a/apps/client/src/app/pages/about/about-page.component.ts
+++ b/apps/client/src/app/pages/about/about-page.component.ts
@@ -41,7 +41,7 @@ export class AboutPageComponent implements OnDestroy, OnInit {
.subscribe((state) => {
this.tabs = [
{
- iconName: 'reader-outline',
+ iconName: 'information-circle-outline',
label: $localize`About`,
path: ['/' + $localize`about`]
},
@@ -53,7 +53,8 @@ export class AboutPageComponent implements OnDestroy, OnInit {
{
iconName: 'ribbon-outline',
label: $localize`License`,
- path: ['/' + $localize`about`, $localize`license`]
+ path: ['/' + $localize`about`, $localize`license`],
+ showCondition: !this.hasPermissionForSubscription
}
];
@@ -64,6 +65,14 @@ export class AboutPageComponent implements OnDestroy, OnInit {
path: ['/' + $localize`about`, $localize`privacy-policy`],
showCondition: this.hasPermissionForSubscription
});
+
+ this.tabs.push({
+ iconName: 'document-text-outline',
+ label: $localize`Terms of Service`,
+ path: ['/' + $localize`about`, $localize`terms-of-service`],
+ showCondition: this.hasPermissionForSubscription
+ });
+
this.user = state.user;
this.changeDetectorRef.markForCheck();
diff --git a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts
index f08b4365d..0dc1aab13 100644
--- a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts
+++ b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts
@@ -3,9 +3,9 @@ import { Subject } from 'rxjs';
@Component({
selector: 'gf-privacy-policy-page',
+ standalone: false,
styleUrls: ['./privacy-policy-page.scss'],
- templateUrl: './privacy-policy-page.html',
- standalone: false
+ templateUrl: './privacy-policy-page.html'
})
export class PrivacyPolicyPageComponent implements OnDestroy {
private unsubscribeSubject = new Subject
();
diff --git a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.scss b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.scss
index b90d23078..4bba9df47 100644
--- a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.scss
+++ b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.scss
@@ -12,6 +12,14 @@
color: rgba(var(--palette-primary-300), 1);
}
}
+
+ h2 {
+ font-size: 1.5rem;
+ }
+
+ h3 {
+ font-size: 1.25rem;
+ }
}
}
}
diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page-routing.module.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page-routing.module.ts
new file mode 100644
index 000000000..4a32e23ed
--- /dev/null
+++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page-routing.module.ts
@@ -0,0 +1,21 @@
+import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
+
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { TermsOfServicePageComponent } from './terms-of-service-page.component';
+
+const routes: Routes = [
+ {
+ canActivate: [AuthGuard],
+ component: TermsOfServicePageComponent,
+ path: '',
+ title: $localize`Terms of Service`
+ }
+];
+
+@NgModule({
+ exports: [RouterModule],
+ imports: [RouterModule.forChild(routes)]
+})
+export class TermsOfServicePageRoutingModule {}
diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts
new file mode 100644
index 000000000..bd4e126ac
--- /dev/null
+++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts
@@ -0,0 +1,17 @@
+import { Component, OnDestroy } from '@angular/core';
+import { Subject } from 'rxjs';
+
+@Component({
+ selector: 'gf-terms-of-service-page',
+ standalone: false,
+ styleUrls: ['./terms-of-service-page.scss'],
+ templateUrl: './terms-of-service-page.html'
+})
+export class TermsOfServicePageComponent implements OnDestroy {
+ private unsubscribeSubject = new Subject();
+
+ public ngOnDestroy() {
+ this.unsubscribeSubject.next();
+ this.unsubscribeSubject.complete();
+ }
+}
diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.html b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.html
new file mode 100644
index 000000000..b178ca138
--- /dev/null
+++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Terms of Service
+
+
+
+
+
diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.module.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.module.ts
new file mode 100644
index 000000000..5861cbb16
--- /dev/null
+++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.module.ts
@@ -0,0 +1,17 @@
+import { CommonModule } from '@angular/common';
+import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
+import { MarkdownModule } from 'ngx-markdown';
+
+import { TermsOfServicePageRoutingModule } from './terms-of-service-page-routing.module';
+import { TermsOfServicePageComponent } from './terms-of-service-page.component';
+
+@NgModule({
+ declarations: [TermsOfServicePageComponent],
+ imports: [
+ CommonModule,
+ MarkdownModule.forChild(),
+ TermsOfServicePageRoutingModule
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class TermsOfServicePageModule {}
diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.scss b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.scss
new file mode 100644
index 000000000..4bba9df47
--- /dev/null
+++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.scss
@@ -0,0 +1,29 @@
+:host {
+ color: rgb(var(--dark-primary-text));
+ display: block;
+
+ ::ng-deep {
+ markdown {
+ a {
+ color: rgba(var(--palette-primary-500), 1);
+ font-weight: 500;
+
+ &:hover {
+ color: rgba(var(--palette-primary-300), 1);
+ }
+ }
+
+ h2 {
+ font-size: 1.5rem;
+ }
+
+ h3 {
+ font-size: 1.25rem;
+ }
+ }
+ }
+}
+
+:host-context(.theme-dark) {
+ color: rgb(var(--light-primary-text));
+}
diff --git a/apps/client/src/app/pages/register/register-page.component.ts b/apps/client/src/app/pages/register/register-page.component.ts
index 66a5f4beb..11b81c32a 100644
--- a/apps/client/src/app/pages/register/register-page.component.ts
+++ b/apps/client/src/app/pages/register/register-page.component.ts
@@ -11,6 +11,7 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
+import { ShowAccessTokenDialogParams } from './show-access-token-dialog/interfaces/interfaces';
import { ShowAccessTokenDialog } from './show-access-token-dialog/show-access-token-dialog.component';
@Component({
@@ -24,6 +25,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
public demoAuthToken: string;
public deviceType: string;
public hasPermissionForSocialLogin: boolean;
+ public hasPermissionForSubscription: boolean;
public hasPermissionToCreateUser: boolean;
public historicalDataItems: LineChartItem[];
public info: InfoItem;
@@ -52,6 +54,10 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
globalPermissions,
permissions.enableSocialLogin
);
+ this.hasPermissionForSubscription = hasPermission(
+ globalPermissions,
+ permissions.enableSubscription
+ );
this.hasPermissionToCreateUser = hasPermission(
globalPermissions,
permissions.createUserAccount
@@ -70,6 +76,10 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
public openShowAccessTokenDialog() {
const dialogRef = this.dialog.open(ShowAccessTokenDialog, {
+ data: {
+ deviceType: this.deviceType,
+ needsToAcceptTermsOfService: this.hasPermissionForSubscription
+ } as ShowAccessTokenDialogParams,
disableClose: true,
height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '30rem'
diff --git a/apps/client/src/app/pages/register/show-access-token-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/register/show-access-token-dialog/interfaces/interfaces.ts
new file mode 100644
index 000000000..c32acac40
--- /dev/null
+++ b/apps/client/src/app/pages/register/show-access-token-dialog/interfaces/interfaces.ts
@@ -0,0 +1,4 @@
+export interface ShowAccessTokenDialogParams {
+ deviceType: string;
+ needsToAcceptTermsOfService: boolean;
+}
diff --git a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts
index c6535bf48..6dd15045b 100644
--- a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts
+++ b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts
@@ -4,12 +4,16 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
+ Inject,
ViewChild
} from '@angular/core';
+import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatStepper } from '@angular/material/stepper';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
+import { ShowAccessTokenDialogParams } from './interfaces/interfaces';
+
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-show-access-token-dialog',
@@ -25,11 +29,16 @@ export class ShowAccessTokenDialog {
public isCreateAccountButtonDisabled = true;
public isDisclaimerChecked = false;
public role: string;
+ public routerLinkAboutTermsOfService = [
+ '/' + $localize`:snake-case:about`,
+ $localize`:snake-case:terms-of-service`
+ ];
private unsubscribeSubject = new Subject();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
+ @Inject(MAT_DIALOG_DATA) public data: ShowAccessTokenDialogParams,
private dataService: DataService
) {}
diff --git a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.html b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.html
index 96c5c967a..7e09c8bbe 100644
--- a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.html
+++ b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.html
@@ -5,7 +5,12 @@
}
-
+
Terms and Conditions
@@ -15,14 +20,28 @@
>
I understand that if I lose my security token, I cannot recover my
- account.
+ @if (data.needsToAcceptTermsOfService) {
+
+ and I agree to the
+ Terms of Service .
+ } @else {
+ .
+ }
@@ -35,7 +54,8 @@
[disabled]="!isDisclaimerChecked"
(click)="createAccount()"
>
- Continue
+ Continue
+
@@ -78,8 +98,7 @@
[disabled]="isCreateAccountButtonDisabled"
[mat-dialog-close]="authToken"
>
- Create Account
-
+ Create Account
diff --git a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts
index 117c1da00..0c7a6fc85 100644
--- a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts
+++ b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts
@@ -9,6 +9,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatStepperModule } from '@angular/material/stepper';
+import { RouterModule } from '@angular/router';
import { ShowAccessTokenDialog } from './show-access-token-dialog.component';
@@ -25,6 +26,7 @@ import { ShowAccessTokenDialog } from './show-access-token-dialog.component';
MatInputModule,
MatStepperModule,
ReactiveFormsModule,
+ RouterModule,
TextFieldModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
diff --git a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.scss b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.scss
index 777c8c854..0198e38bf 100644
--- a/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.scss
+++ b/apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.scss
@@ -1,6 +1,16 @@
:host {
+ --mat-dialog-with-actions-content-padding: 0;
+
+ a {
+ color: rgba(var(--palette-primary-500), 1);
+ font-weight: 500;
+
+ &:hover {
+ color: rgba(var(--palette-primary-300), 1);
+ }
+ }
+
.mat-mdc-dialog-actions {
- padding-left: 0 !important;
- padding-right: 0 !important;
+ padding: 0 !important;
}
}
diff --git a/apps/client/src/assets/privacy-policy.md b/apps/client/src/assets/privacy-policy.md
index 6170c4757..6eea207c3 100644
--- a/apps/client/src/assets/privacy-policy.md
+++ b/apps/client/src/assets/privacy-policy.md
@@ -1,5 +1,3 @@
-Last updated: June 18, 2022
-
This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.
We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.
@@ -16,7 +14,7 @@ For the purposes of this Privacy Policy:
- **Account** means a unique account created for You to access our Service or parts of our Service.
- **Application** means the software program provided by the Company downloaded by You on any electronic device, named Ghostfolio App.
-- **Company** (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to Ghostfolio.
+- **Company** (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to Ghostfolio LLC.
- **Country** refers to: Switzerland
- **Device** means any device that can access the Service such as a computer, a cellphone or a digital tablet.
- **Personal Data** is any information that relates to an identified or identifiable individual.
@@ -78,3 +76,5 @@ You are advised to review this Privacy Policy periodically for any changes. Chan
## Contact Us
If you have any questions about this Privacy Policy, You can contact us [here](https://ghostfol.io/en/about).
+
+Date of Last Revision: March 29, 2025
diff --git a/apps/client/src/assets/terms-of-service.md b/apps/client/src/assets/terms-of-service.md
new file mode 100644
index 000000000..d2a6e598d
--- /dev/null
+++ b/apps/client/src/assets/terms-of-service.md
@@ -0,0 +1,58 @@
+This Terms of Service Agreement (hereinafter referred to as the "Agreement") is a legally binding contract between you (hereinafter referred to as the "User" or "You") and Ghostfolio LLC (hereinafter referred to as "LICENSEE") governing your use of the web application and the application programming interface (API) (hereinafter referred to as the "Service") provided by LICENSEE. By either accessing or using the Service, or by downloading data provided by the Service, you agree to be bound by the terms and conditions of this Agreement. If you do not agree to these terms, please do not access or use the Service.
+
+## Definitions
+
+