Hi,
We've created an internal issue about this. I'll inform you when it is done. Thanks!
Hi,
I've created an issue: https://github.com/abpframework/abp/issues/9343 Please follow that for the progress.
You can set the datatable messages in v4.4-preview. I am closing the question. If you encounter any problem, please let me know. Thanks!
Hi,
We've created an internal issue. Thanks for the reporting.
Hi,
We've added 2FA feature to Angular UI. It will be available in v4.4.
Thanks.
Hi,
Please follow the steps below to replace personal settings component:
yarn ng generate module account
CustomPersonalSettings
:yarn ng generate component account/custom-personal-settings --skip-tests --inline-style
src/account/account.module.ts
content with following:import { NgModule } from '@angular/core';
import { AccountPublicModule } from '@volo/abp.ng.account/public';
import { SharedModule } from '../shared/shared.module';
import { CustomPersonalSettingsComponent } from './custom-personal-settings/custom-personal-settings.component';
@NgModule({
declarations: [CustomPersonalSettingsComponent],
imports: [AccountPublicModule.forChild({}), SharedModule],
})
export class AccountModule {}
src/account/custom-personal-settings/custom-personal-settings.component.ts
content with following:import { ConfigStateService, ProfileService, SubscriptionService } from '@abp/ng.core';
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { Component, EmbeddedViewRef, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
AccountService,
ManageProfileStateService,
ProfileResponse,
} from '@volo/abp.ng.account/public';
import { Observable } from 'rxjs';
import { filter, tap } from 'rxjs/operators';
@Component({
selector: 'app-custom-personal-settings',
templateUrl: './custom-personal-settings.component.html',
styles: [],
})
export class CustomPersonalSettingsComponent implements OnInit {
storedProfile: ProfileResponse;
profile$: Observable<ProfileResponse> = this.manageProfileState.getProfile$();
modalVisible = false;
modalBusy: boolean = false;
modalRef: EmbeddedViewRef<any>;
form: FormGroup;
token: string = '';
isEmailUpdateEnabled: boolean = true;
isUserNameUpdateEnabled: boolean = true;
showEmailVerificationBtn$ = this.manageProfileState.createStateStream(
state => !state.hideEmailVerificationBtn
);
get userId(): string {
return this.configState.getDeep('currentUser.id');
}
askForImmediatePhoneNumberVerification = () => {
this.confirmationService
.info('AbpAccount::DoYouWantToVerifyPhoneNumberMessage', 'AbpAccount::ConfirmYourPhoneNumber')
.pipe(filter(status => status === Confirmation.Status.confirm))
.subscribe(this.initPhoneNumberConfirmation);
};
checkPhoneNumberChanges = ({ phoneNumber }: ProfileResponse) => {
if (phoneNumber && phoneNumber !== this.storedProfile?.phoneNumber) {
this.askForImmediatePhoneNumberVerification();
}
};
buildForm = (profile: ProfileResponse) => {
this.form = this.fb.group({
userName: [profile.userName, [Validators.required, Validators.maxLength(256)]],
email: [profile.email, [Validators.required, Validators.email, Validators.maxLength(256)]],
name: [profile.name || '', [Validators.maxLength(64)]],
surname: [profile.surname || '', [Validators.maxLength(64)]],
phoneNumber: [profile.phoneNumber || '', [Validators.maxLength(16)]],
});
};
initPhoneNumberConfirmation = () => {
this.accountService
.sendPhoneNumberConfirmationToken({
phoneNumber: this.form.value.phoneNumber,
userId: this.userId,
})
.pipe(tap(() => (this.token = '')))
.subscribe(this.openModal);
};
openModal = () => {
this.modalVisible = true;
};
removeModal = () => {
this.modalVisible = false;
};
setPhoneNumberAsConfirmed = () => {
this.storedProfile.phoneNumberConfirmed = true;
};
constructor(
private fb: FormBuilder,
private accountService: AccountService,
private toasterService: ToasterService,
private confirmationService: ConfirmationService,
private configState: ConfigStateService,
private subscription: SubscriptionService,
private manageProfileState: ManageProfileStateService,
private profileService: ProfileService
) {}
confirmPhoneNumber() {
this.accountService
.confirmPhoneNumber({ token: this.token, userId: this.userId })
.pipe(tap(this.setPhoneNumberAsConfirmed), tap(this.removeModal))
.subscribe(() => {
this.toasterService.success('AbpAccount::Verified', '', { life: 5000 });
});
}
sendEmailVerificationToken() {
this.accountService
.sendEmailConfirmationToken({
appName: 'Angular',
email: this.form.value.email,
userId: this.userId,
})
.subscribe(() => {
this.toasterService.success('AbpAccount::EmailConfirmationSentMessage', '', {
messageLocalizationParams: [this.form.value.email],
});
this.manageProfileState.setHideEmailVerificationBtn(true);
});
}
ngOnDestroy() {
this.removeModal();
}
ngOnInit() {
this.subscription.addOne(
this.profile$.pipe(
filter<ProfileResponse>(Boolean),
tap(profile => (this.storedProfile = profile)),
tap(this.checkPhoneNumberChanges)
),
this.buildForm
);
const settings = this.configState.getSettings();
this.isEmailUpdateEnabled =
(settings['Abp.Identity.User.IsEmailUpdateEnabled'] || '').toLowerCase() !== 'false';
this.isUserNameUpdateEnabled =
(settings['Abp.Identity.User.IsUserNameUpdateEnabled'] || '').toLowerCase() !== 'false';
}
submit() {
if (this.form.invalid) return;
const { phoneNumberConfirmed, ...profile } = this.form.value;
this.profileService.update(profile).subscribe(res => {
this.manageProfileState.setProfile(res);
this.toasterService.success('AbpAccount::PersonalSettingsSaved', '', { life: 5000 });
});
}
}
src/account/custom-personal-settings/custom-personal-settings.component.html
content with following:<form validateOnSubmit *ngIf="form" [formGroup]="form" (ngSubmit)="submit()">
<div class="form-group">
<label for="username">{{ 'AbpIdentity::DisplayName:UserName' | abpLocalization }}</label
><span> * </span
><input
type="text"
id="username"
class="form-control"
formControlName="userName"
autofocus
(keydown.space)="$event.preventDefault()"
[readonly]="!isUserNameUpdateEnabled"
/>
</div>
<div class="row">
<div class="col col-md-6">
<div class="form-group">
<label for="name">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</label
><input type="text" id="name" class="form-control" formControlName="name" />
</div>
</div>
<div class="col col-md-6">
<div class="form-group">
<label for="surname">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</label
><input type="text" id="surname" class="form-control" formControlName="surname" />
</div>
</div>
</div>
<div class="form-group">
<label for="email-address">{{ 'AbpIdentity::DisplayName:Email' | abpLocalization }}</label
><span> * </span>
<div class="input-group" validationTarget validationStyle>
<input
type="text"
id="email-address"
class="form-control"
formControlName="email"
[readonly]="!isEmailUpdateEnabled"
/>
<div
*ngIf="{
edited: form.value.email !== storedProfile?.email,
confirmed: storedProfile?.emailConfirmed
} as data"
class="input-group-append"
>
<abp-personal-settings-verify-button
*ngIf="(showEmailVerificationBtn$ | async) || data.edited || data.confirmed"
[verified]="data.confirmed"
[edited]="data.edited"
(btnClick)="sendEmailVerificationToken()"
></abp-personal-settings-verify-button>
</div>
</div>
</div>
<div class="form-group">
<label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label>
<div class="input-group mb-3">
<input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" />
<div class="input-group-append" *ngIf="storedProfile?.phoneNumber">
<abp-personal-settings-verify-button
[verified]="storedProfile?.phoneNumberConfirmed"
[edited]="form.value.phoneNumber !== storedProfile?.phoneNumber"
(btnClick)="initPhoneNumberConfirmation()"
></abp-personal-settings-verify-button>
</div>
</div>
</div>
<abp-button
iconClass="fa fa-check"
buttonClass="btn btn-primary color-white"
buttonType="submit"
[disabled]="form?.invalid"
>
{{ 'AbpIdentity::Save' | abpLocalization }}</abp-button
>
</form>
<abp-modal [(visible)]="modalVisible" [busy]="modalBusy" size="sm">
<ng-template #abpHeader>
<h5>{{ 'AbpAccount::Verify' | abpLocalization }}</h5>
</ng-template>
<ng-template #abpBody>
<form (ngSubmit)="confirmPhoneNumber()">
<div class="mt-2">
<p>
{{ 'AbpAccount::ConfirmationTokenSentMessage' | abpLocalization: form.value.phoneNumber }}
</p>
<div class="form-group">
<label for="confirm-phone-number">{{
'AbpAccount::PhoneConfirmationToken' | abpLocalization
}}</label>
<input
[(ngModel)]="token"
id="confirm-phone-number"
name="confirm-phone-number"
class="form-control"
type="text"
autofocus
/>
</div>
</div>
</form>
</ng-template>
<ng-template #abpFooter>
<button abpClose type="button" class="btn btn-secondary">
{{ 'AbpAccount::Cancel' | abpLocalization }}
</button>
<abp-button type="abp-button" iconClass="fa fa-check" (click)="confirmPhoneNumber()">
{{ 'AbpAccount::Save' | abpLocalization }}
</abp-button>
</ng-template>
</abp-modal>
src/app-routing.module.ts
file modify account
route like this: {
path: 'account',
loadChildren: () => import('./account/account.module').then(m => m.AccountModule),
},
src/app.component.ts
and replace the PersonalSettingsComponent
as shown below:import { Component, OnInit } from '@angular/core';
import {
eAccountManageProfileTabNames,
ManageProfileTabsService,
} from '@volo/abp.ng.account/public/config';
import { CustomPersonalSettingsComponent } from './account/custom-personal-settings/custom-personal-settings.component';
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<abp-dynamic-layout></abp-dynamic-layout>
`,
})
export class AppComponent implements OnInit {
constructor(
private manageProfileTabs: ManageProfileTabsService
) {}
ngOnInit() {
this.manageProfileTabs.patch(eAccountManageProfileTabNames.PersonalInfo, {
component: CustomPersonalSettingsComponent,
});
}
}
You can replace a tab in the my profile page by using the ManageProfileTabsService
.
I hope this helps you.
We've fixed the problem. Please wait for v4.3.2. Thanks.
If I am right, it is related to this ticket
Yes, you can follow the other ticket for the progress. I am closing this one. If you encounter any problem, feel free to open the ticket.
Thanks.
Hi,
You can add an interceptor to get localizations in the language you specify. Please follow the steps below to achieve this:
accept-language.interceptor.ts
import { SessionStateService } from '@abp/ng.core';
import { HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class AcceptLanguageInterceptor implements HttpInterceptor {
constructor(private sessionState: SessionStateService) {}
intercept(request: HttpRequest<any>, next: HttpHandler) {
return next.handle(
request.clone({
setHeaders: this.getAdditionalHeaders(request.headers),
}),
);
}
getAdditionalHeaders(existingHeaders?: HttpHeaders) {
const headers = {} as any;
// When sessionState.getLanguage() returns null at first initialization.
// So you can set your default language as shown below:
const lang = this.sessionState.getLanguage() || 'es' // put your default language here.
if (!existingHeaders?.has('Accept-Language')) {
headers['Accept-Language'] = lang;
}
return headers;
}
}
AppModule
:providers: [
//...
{
provide: HTTP_INTERCEPTORS,
useExisting: AcceptLanguageInterceptor,
multi: true,
},
],
I hope this help you.
Hi,
I created an issue: https://github.com/abpframework/abp/issues/9160
Thanks for the reporting.
Hi,
We did not receive any email. Please email me (mehmet.erim@volosoft.com).