Feature/add ability to close user account (#3444)
* Add ability to close user account * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
KEY_TOKEN,
|
||||
SettingsStorageService
|
||||
} from '@ghostfolio/client/services/settings-storage.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
|
||||
import { downloadAsFile } from '@ghostfolio/common/helper';
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
OnDestroy,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { uniq } from 'lodash';
|
||||
@@ -33,8 +35,13 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
|
||||
public appearancePlaceholder = $localize`Auto`;
|
||||
public baseCurrency: string;
|
||||
public currencies: string[] = [];
|
||||
public deleteOwnUserForm = this.formBuilder.group({
|
||||
accessToken: ['', Validators.required]
|
||||
});
|
||||
public hasPermissionToDeleteOwnUser: boolean;
|
||||
public hasPermissionToUpdateViewMode: boolean;
|
||||
public hasPermissionToUpdateUserSettings: boolean;
|
||||
public isAccessTokenHidden = true;
|
||||
public isWebAuthnEnabled: boolean;
|
||||
public language = document.documentElement.lang;
|
||||
public locales = [
|
||||
@@ -58,7 +65,9 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private dataService: DataService,
|
||||
private formBuilder: FormBuilder,
|
||||
private settingsStorageService: SettingsStorageService,
|
||||
private tokenStorageService: TokenStorageService,
|
||||
private userService: UserService,
|
||||
public webAuthnService: WebAuthnService
|
||||
) {
|
||||
@@ -73,6 +82,11 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.hasPermissionToDeleteOwnUser = hasPermission(
|
||||
this.user.permissions,
|
||||
permissions.deleteOwnUser
|
||||
);
|
||||
|
||||
this.hasPermissionToUpdateUserSettings = hasPermission(
|
||||
this.user.permissions,
|
||||
permissions.updateUserSettings
|
||||
@@ -125,6 +139,33 @@ export class UserAccountSettingsComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
public onCloseAccount() {
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to close your Ghostfolio account?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
this.dataService
|
||||
.deleteOwnUser({
|
||||
accessToken: this.deleteOwnUserForm.get('accessToken').value
|
||||
})
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
alert($localize`Oops! Incorrect Security Token.`);
|
||||
|
||||
return EMPTY;
|
||||
}),
|
||||
takeUntil(this.unsubscribeSubject)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.tokenStorageService.signOut();
|
||||
this.userService.remove();
|
||||
|
||||
document.location.href = `/${document.documentElement.lang}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) {
|
||||
this.dataService
|
||||
.putUserSetting({ isExperimentalFeatures: aEvent.checked })
|
||||
|
@@ -232,6 +232,55 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (hasPermissionToDeleteOwnUser) {
|
||||
<hr class="mt-5" />
|
||||
<form
|
||||
class="w-100"
|
||||
[formGroup]="deleteOwnUserForm"
|
||||
(ngSubmit)="onCloseAccount()"
|
||||
>
|
||||
<div class="d-flex py-1">
|
||||
<div class="pr-1 text-danger w-50" i18n>Danger Zone</div>
|
||||
<div class="pl-1 w-50">
|
||||
<mat-form-field
|
||||
appearance="outline"
|
||||
class="without-hint w-100"
|
||||
[hideRequiredMarker]="true"
|
||||
>
|
||||
<mat-label i18n>Security Token</mat-label>
|
||||
<input
|
||||
formControlName="accessToken"
|
||||
matInput
|
||||
[type]="isAccessTokenHidden ? 'password' : 'text'"
|
||||
/>
|
||||
<button
|
||||
mat-button
|
||||
matSuffix
|
||||
type="button"
|
||||
(click)="isAccessTokenHidden = !isAccessTokenHidden"
|
||||
>
|
||||
<ion-icon
|
||||
[name]="
|
||||
isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline'
|
||||
"
|
||||
/>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
<button
|
||||
class="mt-2"
|
||||
color="warn"
|
||||
mat-flat-button
|
||||
type="submit"
|
||||
[disabled]="
|
||||
!(deleteOwnUserForm.dirty && deleteOwnUserForm.valid)
|
||||
"
|
||||
>
|
||||
<span i18n>Close Account</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { GfValueComponent } from '@ghostfolio/ui/value';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { RouterModule } from '@angular/router';
|
||||
@@ -22,10 +23,12 @@ import { UserAccountSettingsComponent } from './user-account-settings.component'
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatSlideToggleModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule
|
||||
]
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GfUserAccountSettingsModule {}
|
||||
|
@@ -9,6 +9,7 @@ import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||
import { PortfolioHoldingDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-holding-detail.interface';
|
||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
|
||||
import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto';
|
||||
import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface';
|
||||
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
||||
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
@@ -271,6 +272,10 @@ export class DataService {
|
||||
return this.http.delete<any>(`/api/v1/benchmark/${dataSource}/${symbol}`);
|
||||
}
|
||||
|
||||
public deleteOwnUser(aData: DeleteOwnUserDto) {
|
||||
return this.http.delete<any>(`/api/v1/user`, { body: aData });
|
||||
}
|
||||
|
||||
public deleteUser(aId: string) {
|
||||
return this.http.delete<any>(`/api/v1/user/${aId}`);
|
||||
}
|
||||
|
Reference in New Issue
Block a user