Browse Source

Feature/restructure user account registration flow (#4393)

* Restructure user account registration flow

* Update changelog
pull/4430/head
Tobias Kugel 1 week ago
committed by GitHub
parent
commit
a137bbbdca
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 25
      apps/client/src/app/pages/register/register-page.component.ts
  3. 2
      apps/client/src/app/pages/register/register-page.html
  4. 57
      apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts
  5. 125
      apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.html
  6. 4
      apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts
  7. 4
      apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.scss

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Improved the usability of the user account registration
## 2.145.1 - 2025-03-10
### Added

25
apps/client/src/app/pages/register/register-page.component.ts

@ -7,7 +7,6 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { Role } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -59,15 +58,6 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
);
}
public async createAccount() {
this.dataService
.postUser()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ accessToken, authToken, role }) => {
this.openShowAccessTokenDialog(accessToken, authToken, role);
});
}
public async onLoginWithInternetIdentity() {
try {
const { authToken } = await this.internetIdentityService.login();
@ -76,17 +66,8 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
} catch {}
}
public openShowAccessTokenDialog(
accessToken: string,
authToken: string,
role: Role
) {
public openShowAccessTokenDialog() {
const dialogRef = this.dialog.open(ShowAccessTokenDialog, {
data: {
accessToken,
authToken,
role
},
disableClose: true,
width: '30rem'
});
@ -94,8 +75,8 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((data) => {
if (data?.authToken) {
.subscribe((authToken) => {
if (authToken) {
this.tokenStorageService.saveToken(authToken, true);
this.router.navigate(['/']);

2
apps/client/src/app/pages/register/register-page.html

@ -22,7 +22,7 @@
class="d-inline-block"
color="primary"
mat-flat-button
(click)="createAccount()"
(click)="openShowAccessTokenDialog()"
>
<ng-container i18n>Create Account</ng-container>
</button>

57
apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.component.ts

@ -1,19 +1,58 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { DataService } from '@ghostfolio/client/services/data.service';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ViewChild
} from '@angular/core';
import { MatStepper } from '@angular/material/stepper';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-show-access-token-dialog',
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-show-access-token-dialog',
standalone: false,
styleUrls: ['./show-access-token-dialog.scss'],
templateUrl: 'show-access-token-dialog.html',
standalone: false
templateUrl: 'show-access-token-dialog.html'
})
export class ShowAccessTokenDialog {
public isAgreeButtonDisabled = true;
@ViewChild(MatStepper) stepper!: MatStepper;
public accessToken: string;
public authToken: string;
public isCreateAccountButtonDisabled = true;
public isDisclaimerChecked = false;
public role: string;
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService
) {}
public constructor(@Inject(MAT_DIALOG_DATA) public data: any) {}
public createAccount() {
this.dataService
.postUser()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ accessToken, authToken, role }) => {
this.accessToken = accessToken;
this.authToken = authToken;
this.role = role;
this.stepper.next();
this.changeDetectorRef.markForCheck();
});
}
public enableCreateAccountButton() {
this.isCreateAccountButtonDisabled = false;
}
public enableAgreeButton() {
this.isAgreeButtonDisabled = false;
public onChangeDislaimerChecked() {
this.isDisclaimerChecked = !this.isDisclaimerChecked;
}
}

125
apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.html

@ -1,48 +1,93 @@
<h1 mat-dialog-title>
<span i18n>Create Account</span>
@if (data.role === 'ADMIN') {
<span class="badge badge-light ml-2">{{ data.role }}</span>
@if (role === 'ADMIN') {
<span class="badge badge-light ml-2">{{ role }}</span>
}
</h1>
<div class="py-3" mat-dialog-content>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Security Token</mat-label>
<textarea
cdkTextareaAutosize
matInput
readonly
type="text"
[(value)]="data.accessToken"
></textarea>
<div class="float-right mt-3">
<button
color="secondary"
mat-flat-button
[cdkCopyToClipboard]="data.accessToken"
(click)="enableAgreeButton()"
<div class="px-0" mat-dialog-content>
<mat-stepper #stepper animationDuration="0ms" linear>
<mat-step editable="false" [completed]="isDisclaimerChecked">
<ng-template i18n matStepLabel>Terms and Conditions</ng-template>
<div class="pt-2">
<ng-container i18n
>Please keep your security token safe. If you lose it, you will not be
able to recover your account.</ng-container
>
<ion-icon class="mr-1" name="copy-outline" /><span i18n
>Copy to clipboard</span
</div>
<mat-checkbox
class="pt-2"
color="primary"
(change)="onChangeDislaimerChecked()"
>
<ng-container i18n
>I understand that if I lose my security token, I cannot recover my
account.</ng-container
>
</mat-checkbox>
<div class="mt-3" mat-dialog-actions>
<div class="flex-grow-1">
<button i18n mat-button [mat-dialog-close]="undefined">Cancel</button>
</div>
<div>
<button
color="primary"
mat-flat-button
[disabled]="!isDisclaimerChecked"
(click)="createAccount()"
>
</button>
<ng-container i18n>Continue</ng-container>
</button>
</div>
</div>
</mat-form-field>
</div>
<p i18n>
I agree to have stored my <i>Security Token</i> from above in a secure
place. If I lose it, I cannot get my account back.
</p>
</div>
<div class="justify-content-end" mat-dialog-actions>
<button i18n mat-flat-button [mat-dialog-close]="undefined">Cancel</button>
<button
color="primary"
mat-flat-button
[disabled]="isAgreeButtonDisabled"
[mat-dialog-close]="data"
>
<span i18n>Agree and continue</span>
<ion-icon class="ml-1" name="arrow-forward-outline" />
</button>
</mat-step>
<mat-step editable="false">
<ng-template i18n matStepLabel>Security Token</ng-template>
<div class="pt-2">
<ng-container i18n
>Here is your security token. It is only visible once, please store
and keep it in a safe place.</ng-container
>
</div>
<mat-form-field appearance="outline" class="pt-3 w-100 without-hint">
<mat-label i18n>Security Token</mat-label>
<textarea
cdkTextareaAutosize
matInput
readonly
type="text"
[(value)]="accessToken"
></textarea>
<div class="float-right mt-1">
<button
color="secondary"
mat-flat-button
[cdkCopyToClipboard]="accessToken"
(click)="enableCreateAccountButton()"
>
<ion-icon class="mr-1" name="copy-outline" />
<span i18n>Copy to clipboard</span>
</button>
</div>
</mat-form-field>
<div align="end" class="mt-1" mat-dialog-actions>
<div>
<button
color="primary"
mat-flat-button
matStepperNext
[disabled]="isCreateAccountButtonDisabled"
[mat-dialog-close]="authToken"
>
<span i18n>Create Account</span>
<ion-icon class="ml-1" name="arrow-forward-outline" />
</button>
</div>
</div>
</mat-step>
<ng-template matStepperIcon="done">
<ion-icon name="checkmark-outline"></ion-icon>
</ng-template>
</mat-stepper>
<div></div>
</div>

4
apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.module.ts

@ -4,9 +4,11 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatStepperModule } from '@angular/material/stepper';
import { ShowAccessTokenDialog } from './show-access-token-dialog.component';
@ -17,9 +19,11 @@ import { ShowAccessTokenDialog } from './show-access-token-dialog.component';
CommonModule,
FormsModule,
MatButtonModule,
MatCheckboxModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
MatStepperModule,
ReactiveFormsModule,
TextFieldModule
],

4
apps/client/src/app/pages/register/show-access-token-dialog/show-access-token-dialog.scss

@ -1,2 +1,6 @@
:host {
.mat-mdc-dialog-actions {
padding-left: 0 !important;
padding-right: 0 !important;
}
}

Loading…
Cancel
Save