Browse Source

Task/improve user account deletion flow (#7269)

* Improve user account deletion flow

* Update changelog
pull/7271/head^2
Thomas Kaul 7 days ago
committed by GitHub
parent
commit
45ab4016e5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 8
      apps/api/src/app/user/user.service.ts
  3. 9
      apps/client/src/app/components/user-account-settings/user-account-settings.component.ts
  4. 110
      apps/client/src/app/components/user-account-settings/user-account-settings.html
  5. 1
      libs/common/src/lib/permissions.ts

1
CHANGELOG.md

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the user account deletion flow in the user settings of the user account page
- Set the change detection strategy to `OnPush` in the _FIRE_ page
- Set the change detection strategy to `OnPush` in the users section of the admin control panel
- Improved the language localization for Dutch (`nl`)

8
apps/api/src/app/user/user.service.ts

@ -535,6 +535,14 @@ export class UserService {
user.subscription.offer.label = undefined;
}
if (
!hasRole(user, Role.DEMO) &&
(user.provider !== 'ANONYMOUS' ||
user.subscription?.type === SubscriptionType.Premium)
) {
currentPermissions.push(permissions.requestOwnUserDeletion);
}
if (hasRole(user, Role.ADMIN)) {
currentPermissions.push(permissions.syncDemoUserAccount);
}

9
apps/client/src/app/components/user-account-settings/user-account-settings.component.ts

@ -71,11 +71,13 @@ import { catchError } from 'rxjs/operators';
export class GfUserAccountSettingsComponent implements OnInit {
public appearancePlaceholder = $localize`Auto`;
public baseCurrency: string;
public closeUserAccountMail: string;
public currencies: string[] = [];
public deleteOwnUserForm = this.formBuilder.group({
accessToken: ['', Validators.required]
});
public hasPermissionToDeleteOwnUser: boolean;
public hasPermissionToRequestOwnUserDeletion: boolean;
public hasPermissionToUpdateViewMode: boolean;
public hasPermissionToUpdateUserSettings: boolean;
public isAccessTokenHidden = true;
@ -124,11 +126,18 @@ export class GfUserAccountSettingsComponent implements OnInit {
if (state?.user) {
this.user = state.user;
this.closeUserAccountMail = `mailto:hi@ghostfol.io?subject=Delete Account&body=Hello%0D%0DPlease delete my Ghostfolio account.%0D%0DUser ID: ${this.user.id}%0D%0DKind regards`;
this.hasPermissionToDeleteOwnUser = hasPermission(
this.user.permissions,
permissions.deleteOwnUser
);
this.hasPermissionToRequestOwnUserDeletion = hasPermission(
this.user.permissions,
permissions.requestOwnUserDeletion
);
this.hasPermissionToUpdateUserSettings = hasPermission(
this.user.permissions,
permissions.updateUserSettings

110
apps/client/src/app/components/user-account-settings/user-account-settings.html

@ -280,61 +280,81 @@
<div class="align-items-center d-flex py-1">
<div class="pr-1 w-50"></div>
<div class="pl-1 text-monospace w-50">
<button color="primary" mat-flat-button (click)="onExport()">
<span i18n>Export Data</span>
<button color="primary" i18n mat-flat-button (click)="onExport()">
Export Data
</button>
</div>
</div>
@if (hasPermissionToDeleteOwnUser) {
@if (
hasPermissionToDeleteOwnUser || hasPermissionToRequestOwnUserDeletion
) {
<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"
<div class="d-flex py-1">
<div class="pr-1 text-danger w-50" i18n>Danger Zone</div>
<div class="pl-1 w-50">
@if (hasPermissionToDeleteOwnUser) {
<form
class="w-100"
[formGroup]="deleteOwnUserForm"
(ngSubmit)="onCloseAccount()"
>
<mat-label i18n>Security Token</mat-label>
<input
formControlName="accessToken"
matInput
[type]="isAccessTokenHidden ? 'password' : 'text'"
/>
<button
class="mx-2"
mat-icon-button
matSuffix
type="button"
(click)="isAccessTokenHidden = !isAccessTokenHidden"
<mat-form-field
appearance="outline"
class="without-hint w-100"
[hideRequiredMarker]="true"
>
<ion-icon
class="mat-icon"
[name]="
isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline'
"
<mat-label i18n>Security Token</mat-label>
<input
formControlName="accessToken"
matInput
[type]="isAccessTokenHidden ? 'password' : 'text'"
/>
<button
class="mx-2"
mat-icon-button
matSuffix
type="button"
(click)="isAccessTokenHidden = !isAccessTokenHidden"
>
<ion-icon
class="mat-icon"
[name]="
isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline'
"
/>
</button>
</mat-form-field>
<button
class="mt-2"
color="warn"
i18n
mat-flat-button
type="submit"
[disabled]="
!(deleteOwnUserForm.dirty && deleteOwnUserForm.valid)
"
>
Close Account
</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>
</form>
} @else if (
hasPermissionToRequestOwnUserDeletion &&
(user?.accounts?.length > 0 || user?.activitiesCount > 0)
) {
<button color="warn" i18n mat-flat-button [disabled]="true">
Close Account
</button>
</div>
<div class="hint-text mt-2 text-muted" i18n>
For security reasons, please delete all activities and accounts
first before your Ghostfolio account can be closed.
</div>
} @else if (hasPermissionToRequestOwnUserDeletion) {
<a color="warn" i18n mat-flat-button [href]="closeUserAccountMail"
>Close Account</a
>
}
</div>
</form>
</div>
}
</div>
</div>

1
libs/common/src/lib/permissions.ts

@ -50,6 +50,7 @@ export const permissions = {
readTags: 'readTags',
readWatchlist: 'readWatchlist',
reportDataGlitch: 'reportDataGlitch',
requestOwnUserDeletion: 'requestOwnUserDeletion',
syncDemoUserAccount: 'syncDemoUserAccount',
toggleReadOnlyMode: 'toggleReadOnlyMode',
updateAccount: 'updateAccount',

Loading…
Cancel
Save