PM-2047 Migrated Change Avatar component (#8522)

* PM-2047 Migrated Change Avatar component

* PM-2047 Addressed the review comments

* PM-2047 Changed the file name

* PM-2047 Removed form promise
This commit is contained in:
KiruthigaManivannan 2024-06-06 19:23:02 +05:30 committed by GitHub
parent d58103dfb1
commit a1442194ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 101 additions and 134 deletions

View File

@ -0,0 +1,61 @@
<bit-dialog dialogSize="large" [title]="'customizeAvatar' | i18n">
<ng-container bitDialogContent>
<div class="tw-text-center" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<app-callout type="error" *ngIf="error">
{{ error }}
</app-callout>
<p class="tw-text-lg">{{ "pickAnAvatarColor" | i18n }}</p>
<div class="tw-flex tw-flex-wrap tw-justify-center tw-gap-8">
<ng-container *ngFor="let c of defaultColorPalette">
<selectable-avatar
appStopClick
(select)="setSelection(c.color)"
[selected]="c.selected"
[title]="c.name"
text="{{ profile | userName }}"
[color]="c.color"
[border]="true"
>
</selectable-avatar>
</ng-container>
<span>
<span
[tabIndex]="0"
(keyup.enter)="showCustomPicker()"
(click)="showCustomPicker()"
title="{{ 'customColor' | i18n }}"
[ngClass]="{
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
customColorSelected
}"
class="tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
[style.background-color]="customColor$ | async"
>
<i
[style.color]="customTextColor$ | async"
class="bwi bwi-pencil tw-m-auto tw-text-3xl"
></i>
<input
tabindex="-1"
class="tw-absolute tw-bottom-0 tw-right-0 tw-h-px tw-w-px tw-border-none tw-bg-transparent tw-opacity-0"
#colorPicker
type="color"
[ngModel]="customColor$ | async"
(ngModelChange)="customColor$.next($event)"
/>
</span>
</span>
</div>
</ng-container>
<ng-container bitDialogFooter>
<button bitButton type="submit" buttonType="primary" [disabled]="loading" [bitAction]="submit">
{{ "save" | i18n }}
</button>
<button bitButton type="button" buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@ -1,11 +1,10 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { import {
Component, Component,
ElementRef, ElementRef,
EventEmitter, Inject,
Input,
OnDestroy, OnDestroy,
OnInit, OnInit,
Output,
ViewChild, ViewChild,
ViewEncapsulation, ViewEncapsulation,
} from "@angular/core"; } from "@angular/core";
@ -14,20 +13,20 @@ import { BehaviorSubject, debounceTime, firstValueFrom, Subject, takeUntil } fro
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { DialogService } from "@bitwarden/components";
type ChangeAvatarDialogData = {
profile: ProfileResponse;
};
@Component({ @Component({
selector: "app-change-avatar", templateUrl: "change-avatar-dialog.component.html",
templateUrl: "change-avatar.component.html",
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
}) })
export class ChangeAvatarComponent implements OnInit, OnDestroy { export class ChangeAvatarDialogComponent implements OnInit, OnDestroy {
@Input() profile: ProfileResponse; profile: ProfileResponse;
@Output() changeColor: EventEmitter<string | null> = new EventEmitter();
@Output() onSaved = new EventEmitter();
@ViewChild("colorPicker") colorPickerElement: ElementRef<HTMLElement>; @ViewChild("colorPicker") colorPickerElement: ElementRef<HTMLElement>;
@ -52,11 +51,14 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
@Inject(DIALOG_DATA) protected data: ChangeAvatarDialogData,
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private logService: LogService,
private avatarService: AvatarService, private avatarService: AvatarService,
) {} private dialogRef: DialogRef,
) {
this.profile = data.profile;
}
async ngOnInit() { async ngOnInit() {
//localize the default colors //localize the default colors
@ -88,20 +90,15 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
Utils.stringToColor(this.profile.name.toString()); Utils.stringToColor(this.profile.name.toString());
} }
async submit() { submit = async () => {
try {
if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) { if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) {
await this.avatarService.setAvatarColor(this.currentSelection); await this.avatarService.setAvatarColor(this.currentSelection);
this.changeColor.emit(this.currentSelection); this.dialogRef.close();
this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated")); this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated"));
} else { } else {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
} }
} catch (e) { };
this.logService.error(e);
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
async ngOnDestroy() { async ngOnDestroy() {
this.destroy$.next(); this.destroy$.next();
@ -131,6 +128,10 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy {
} }
} }
} }
static open(dialogService: DialogService, config: DialogConfig<ChangeAvatarDialogData>) {
return dialogService.open(ChangeAvatarDialogComponent, config);
}
} }
export class NamedAvatarColor { export class NamedAvatarColor {

View File

@ -1,84 +0,0 @@
<!-- Please remove this disable statement when editing this file! -->
<!-- eslint-disable tailwindcss/no-custom-classname -->
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="customizeTitle">
<div class="modal-dialog modal-dialog-scrollable tw-w-[600px] tw-max-w-none" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="customizeTitle">{{ "customizeAvatar" | i18n }}</h2>
<button
type="button"
class="close"
data-dismiss="modal"
appA11yTitle="{{ 'close' | i18n }}"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card-body text-center" *ngIf="loading">
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
{{ "loading" | i18n }}
</div>
<app-callout type="error" *ngIf="error">
{{ error }}
</app-callout>
<p class="tw-text-lg">{{ "pickAnAvatarColor" | i18n }}</p>
<div class="tw-flex tw-flex-wrap tw-justify-center tw-gap-8">
<ng-container *ngFor="let c of defaultColorPalette">
<selectable-avatar
appStopClick
(select)="setSelection(c.color)"
[selected]="c.selected"
[title]="c.name"
text="{{ profile | userName }}"
[color]="c.color"
[border]="true"
>
</selectable-avatar>
</ng-container>
<span>
<span
[tabIndex]="0"
(keyup.enter)="showCustomPicker()"
(click)="showCustomPicker()"
title="{{ 'customColor' | i18n }}"
[ngClass]="{
'!tw-outline-[3px] tw-outline-primary-600 hover:tw-outline-[3px] hover:tw-outline-primary-600':
customColorSelected
}"
class="tw-outline-solid tw-bg-white tw-relative tw-flex tw-h-24 tw-w-24 tw-cursor-pointer tw-place-content-center tw-content-center tw-justify-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600"
[style.background-color]="customColor$ | async"
>
<i
[style.color]="customTextColor$ | async"
class="bwi bwi-pencil tw-m-auto tw-text-3xl"
></i>
<input
tabindex="-1"
class="tw-absolute tw-bottom-0 tw-right-0 tw-h-px tw-w-px tw-border-none tw-bg-transparent tw-opacity-0"
#colorPicker
type="color"
[ngModel]="customColor$ | async"
(ngModelChange)="customColor$.next($event)"
/>
</span>
</span>
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary btn-submit"
[disabled]="loading"
(click)="submit()"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "save" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">
{{ "close" | i18n }}
</button>
</div>
</div>
</div>
</div>

View File

@ -45,4 +45,3 @@
</div> </div>
<button bitButton bitFormButton type="submit" buttonType="primary">{{ "save" | i18n }}</button> <button bitButton bitFormButton type="submit" buttonType="primary">{{ "save" | i18n }}</button>
</form> </form>
<ng-template #avatarModalTemplate></ng-template>

View File

@ -1,28 +1,25 @@
import { ViewChild, ViewContainerRef, Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from "@angular/forms";
import { Subject, takeUntil } from "rxjs"; import { Subject, takeUntil } from "rxjs";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request";
import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { DialogService } from "@bitwarden/components";
import { ChangeAvatarComponent } from "./change-avatar.component"; import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component";
@Component({ @Component({
selector: "app-profile", selector: "app-profile",
templateUrl: "profile.component.html", templateUrl: "profile.component.html",
}) })
export class ProfileComponent implements OnInit, OnDestroy { export class ProfileComponent implements OnInit {
loading = true; loading = true;
profile: ProfileResponse; profile: ProfileResponse;
fingerprintMaterial: string; fingerprintMaterial: string;
@ViewChild("avatarModalTemplate", { read: ViewContainerRef, static: true })
avatarModalRef: ViewContainerRef;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
protected formGroup = new FormGroup({ protected formGroup = new FormGroup({
@ -35,7 +32,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService, private platformUtilsService: PlatformUtilsService,
private stateService: StateService, private stateService: StateService,
private modalService: ModalService, private dialogService: DialogService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -53,24 +50,17 @@ export class ProfileComponent implements OnInit, OnDestroy {
}); });
} }
openChangeAvatar = async () => {
ChangeAvatarDialogComponent.open(this.dialogService, {
data: { profile: this.profile },
});
};
async ngOnDestroy() { async ngOnDestroy() {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
} }
openChangeAvatar = async () => {
const modalOpened = await this.modalService.openViewRef(
ChangeAvatarComponent,
this.avatarModalRef,
(modal) => {
modal.profile = this.profile;
modal.changeColor.pipe(takeUntil(this.destroy$)).subscribe(() => {
modalOpened[0].close();
});
},
);
};
submit = async () => { submit = async () => {
const request = new UpdateProfileRequest( const request = new UpdateProfileRequest(
this.formGroup.get("name").value, this.formGroup.get("name").value,

View File

@ -28,7 +28,7 @@ import { RegisterFormModule } from "../auth/register-form/register-form.module";
import { RemovePasswordComponent } from "../auth/remove-password.component"; import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component"; import { SetPasswordComponent } from "../auth/set-password.component";
import { AccountComponent } from "../auth/settings/account/account.component"; import { AccountComponent } from "../auth/settings/account/account.component";
import { ChangeAvatarComponent } from "../auth/settings/account/change-avatar.component"; import { ChangeAvatarDialogComponent } from "../auth/settings/account/change-avatar-dialog.component";
import { ChangeEmailComponent } from "../auth/settings/account/change-email.component"; import { ChangeEmailComponent } from "../auth/settings/account/change-email.component";
import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component"; import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component";
import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component"; import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component";
@ -156,7 +156,7 @@ import { SharedModule } from "./shared.module";
PreferencesComponent, PreferencesComponent,
PremiumBadgeComponent, PremiumBadgeComponent,
ProfileComponent, ProfileComponent,
ChangeAvatarComponent, ChangeAvatarDialogComponent,
ProvidersComponent, ProvidersComponent,
PurgeVaultComponent, PurgeVaultComponent,
RecoverDeleteComponent, RecoverDeleteComponent,
@ -230,7 +230,7 @@ import { SharedModule } from "./shared.module";
PreferencesComponent, PreferencesComponent,
PremiumBadgeComponent, PremiumBadgeComponent,
ProfileComponent, ProfileComponent,
ChangeAvatarComponent, ChangeAvatarDialogComponent,
ProvidersComponent, ProvidersComponent,
PurgeVaultComponent, PurgeVaultComponent,
RecoverDeleteComponent, RecoverDeleteComponent,