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 ### 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 _FIRE_ page
- Set the change detection strategy to `OnPush` in the users section of the admin control panel - Set the change detection strategy to `OnPush` in the users section of the admin control panel
- Improved the language localization for Dutch (`nl`) - 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; 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)) { if (hasRole(user, Role.ADMIN)) {
currentPermissions.push(permissions.syncDemoUserAccount); 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 { export class GfUserAccountSettingsComponent implements OnInit {
public appearancePlaceholder = $localize`Auto`; public appearancePlaceholder = $localize`Auto`;
public baseCurrency: string; public baseCurrency: string;
public closeUserAccountMail: string;
public currencies: string[] = []; public currencies: string[] = [];
public deleteOwnUserForm = this.formBuilder.group({ public deleteOwnUserForm = this.formBuilder.group({
accessToken: ['', Validators.required] accessToken: ['', Validators.required]
}); });
public hasPermissionToDeleteOwnUser: boolean; public hasPermissionToDeleteOwnUser: boolean;
public hasPermissionToRequestOwnUserDeletion: boolean;
public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateViewMode: boolean;
public hasPermissionToUpdateUserSettings: boolean; public hasPermissionToUpdateUserSettings: boolean;
public isAccessTokenHidden = true; public isAccessTokenHidden = true;
@ -124,11 +126,18 @@ export class GfUserAccountSettingsComponent implements OnInit {
if (state?.user) { if (state?.user) {
this.user = 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.hasPermissionToDeleteOwnUser = hasPermission(
this.user.permissions, this.user.permissions,
permissions.deleteOwnUser permissions.deleteOwnUser
); );
this.hasPermissionToRequestOwnUserDeletion = hasPermission(
this.user.permissions,
permissions.requestOwnUserDeletion
);
this.hasPermissionToUpdateUserSettings = hasPermission( this.hasPermissionToUpdateUserSettings = hasPermission(
this.user.permissions, this.user.permissions,
permissions.updateUserSettings 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="align-items-center d-flex py-1">
<div class="pr-1 w-50"></div> <div class="pr-1 w-50"></div>
<div class="pl-1 text-monospace w-50"> <div class="pl-1 text-monospace w-50">
<button color="primary" mat-flat-button (click)="onExport()"> <button color="primary" i18n mat-flat-button (click)="onExport()">
<span i18n>Export Data</span> Export Data
</button> </button>
</div> </div>
</div> </div>
@if (hasPermissionToDeleteOwnUser) { @if (
hasPermissionToDeleteOwnUser || hasPermissionToRequestOwnUserDeletion
) {
<hr class="mt-5" /> <hr class="mt-5" />
<form <div class="d-flex py-1">
class="w-100" <div class="pr-1 text-danger w-50" i18n>Danger Zone</div>
[formGroup]="deleteOwnUserForm" <div class="pl-1 w-50">
(ngSubmit)="onCloseAccount()" @if (hasPermissionToDeleteOwnUser) {
> <form
<div class="d-flex py-1"> class="w-100"
<div class="pr-1 text-danger w-50" i18n>Danger Zone</div> [formGroup]="deleteOwnUserForm"
<div class="pl-1 w-50"> (ngSubmit)="onCloseAccount()"
<mat-form-field
appearance="outline"
class="without-hint w-100"
[hideRequiredMarker]="true"
> >
<mat-label i18n>Security Token</mat-label> <mat-form-field
<input appearance="outline"
formControlName="accessToken" class="without-hint w-100"
matInput [hideRequiredMarker]="true"
[type]="isAccessTokenHidden ? 'password' : 'text'"
/>
<button
class="mx-2"
mat-icon-button
matSuffix
type="button"
(click)="isAccessTokenHidden = !isAccessTokenHidden"
> >
<ion-icon <mat-label i18n>Security Token</mat-label>
class="mat-icon" <input
[name]=" formControlName="accessToken"
isAccessTokenHidden ? 'eye-outline' : 'eye-off-outline' 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> </button>
</mat-form-field> </form>
<button } @else if (
class="mt-2" hasPermissionToRequestOwnUserDeletion &&
color="warn" (user?.accounts?.length > 0 || user?.activitiesCount > 0)
mat-flat-button ) {
type="submit" <button color="warn" i18n mat-flat-button [disabled]="true">
[disabled]=" Close Account
!(deleteOwnUserForm.dirty && deleteOwnUserForm.valid)
"
>
<span i18n>Close Account</span>
</button> </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> </div>
</form> </div>
} }
</div> </div>
</div> </div>

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

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

Loading…
Cancel
Save