diff --git a/docs/en/Modules/OpenIddict.md b/docs/en/Modules/OpenIddict.md index 2c8ae7572e..22cb22bf55 100644 --- a/docs/en/Modules/OpenIddict.md +++ b/docs/en/Modules/OpenIddict.md @@ -330,7 +330,19 @@ public class MyClaimDestinationsProvider : IAbpOpenIddictClaimDestinationsProvid { public virtual Task SetDestinationsAsync(AbpOpenIddictClaimDestinationsProviderContext context) { - // ... + foreach (var claim in context.Claims) + { + if (claim.Type == MyClaims.MyClaimsType) + { + claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken); + } + + if (claim.Type == MyClaims.MyClaimsType2) + { + claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken); + } + } + return Task.CompletedTask; } } diff --git a/npm/ng-packs/packages/account/src/lib/account-routing.module.ts b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts index c09dede3cb..e4b18a717b 100644 --- a/npm/ng-packs/packages/account/src/lib/account-routing.module.ts +++ b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts @@ -13,6 +13,8 @@ import { RegisterComponent } from './components/register/register.component'; import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; import { eAccountComponents } from './enums/components'; import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; +import { AccountExtensionsGuard } from './guards'; + const routes: Routes = [ { path: '', pathMatch: 'full', redirectTo: 'login' }, @@ -68,7 +70,7 @@ const routes: Routes = [ { path: 'manage', component: ReplaceableRouteContainerComponent, - canActivate: [AuthGuard], + canActivate: [AuthGuard, AccountExtensionsGuard], data: { replaceableComponent: { key: eAccountComponents.ManageProfile, diff --git a/npm/ng-packs/packages/account/src/lib/account.module.ts b/npm/ng-packs/packages/account/src/lib/account.module.ts index bfa37d4bfa..bdf3c39792 100644 --- a/npm/ng-packs/packages/account/src/lib/account.module.ts +++ b/npm/ng-packs/packages/account/src/lib/account.module.ts @@ -15,6 +15,10 @@ import { accountConfigOptionsFactory } from './utils/factory-utils'; import { AuthenticationFlowGuard } from './guards/authentication-flow.guard'; import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component'; import { ResetPasswordComponent } from './components/reset-password/reset-password.component'; +import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions'; +import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS } from './tokens/extensions.token'; +import { AccountExtensionsGuard } from './guards/extensions.guard'; +import { PersonalSettingsHalfRowComponent } from './components/personal-settings/personal-settings-half-row.component'; const declarations = [ LoginComponent, @@ -24,6 +28,7 @@ const declarations = [ PersonalSettingsComponent, ForgotPasswordComponent, ResetPasswordComponent, + PersonalSettingsHalfRowComponent, ]; @NgModule({ @@ -34,6 +39,7 @@ const declarations = [ ThemeSharedModule, NgbDropdownModule, NgxValidateCoreModule, + UiExtensionsModule, ], exports: [...declarations], }) @@ -49,6 +55,11 @@ export class AccountModule { useFactory: accountConfigOptionsFactory, deps: [ACCOUNT_CONFIG_OPTIONS], }, + { + provide: ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, + useValue: options.editFormPropContributors, + }, + AccountExtensionsGuard, ], }; } diff --git a/npm/ng-packs/packages/account/src/lib/components/index.ts b/npm/ng-packs/packages/account/src/lib/components/index.ts index 098ee8a202..d74263b052 100644 --- a/npm/ng-packs/packages/account/src/lib/components/index.ts +++ b/npm/ng-packs/packages/account/src/lib/components/index.ts @@ -4,4 +4,5 @@ export * from './login/login.component'; export * from './manage-profile/manage-profile.component'; export * from './register/register.component'; export * from './personal-settings/personal-settings.component'; +export * from './personal-settings/personal-settings-half-row.component'; export * from './reset-password/reset-password.component'; diff --git a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts new file mode 100644 index 0000000000..a000e353d6 --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings-half-row.component.ts @@ -0,0 +1,37 @@ +import { Component, Inject } from '@angular/core'; +import { + EXTENSIONS_FORM_PROP, + FormProp, + EXTENSIBLE_FORM_VIEW_PROVIDER, +} from '@abp/ng.theme.shared/extensions'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'abp-personal-settings-half-row', + template: `
+
+ + +
+
`, + styles: [], + viewProviders: [EXTENSIBLE_FORM_VIEW_PROVIDER], +}) +export class PersonalSettingsHalfRowComponent { + public displayName: string; + public name: string; + public id: string; + public formGroup: FormGroup; + + constructor(@Inject(EXTENSIONS_FORM_PROP) private propData: FormProp) { + this.displayName = propData.displayName; + this.name = propData.name; + this.id = propData.id; + } +} diff --git a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html index 0b70ad41e8..e166a49545 100644 --- a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html +++ b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html @@ -1,55 +1,11 @@ -
-
- * -
-
-
-
- -
-
-
-
- -
-
-
-
- * -
-
- -
+ + + {{ 'AbpIdentity::Save' | abpLocalization }} diff --git a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts index d15df99878..1688ef5741 100644 --- a/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts +++ b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts @@ -1,17 +1,27 @@ -import { ProfileService } from '@abp/ng.account.core/proxy'; +import { ProfileDto, ProfileService } from '@abp/ng.account.core/proxy'; import { ToasterService } from '@abp/ng.theme.shared'; -import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Component, Injector, OnInit, ViewEncapsulation } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { finalize } from 'rxjs/operators'; import { Account } from '../../models/account'; import { ManageProfileStateService } from '../../services/manage-profile.state.service'; - -const { maxLength, required, email } = Validators; +import { + EXTENSIONS_IDENTIFIER, + FormPropData, + generateFormFromProps, +} from '@abp/ng.theme.shared/extensions'; +import { eAccountComponents } from '../../enums'; @Component({ selector: 'abp-personal-settings-form', templateUrl: './personal-settings.component.html', exportAs: 'abpPersonalSettingsForm', + providers: [ + { + provide: EXTENSIONS_IDENTIFIER, + useValue: eAccountComponents.PersonalSettings, + }, + ], }) export class PersonalSettingsComponent implements @@ -19,6 +29,8 @@ export class PersonalSettingsComponent Account.PersonalSettingsComponentInputs, Account.PersonalSettingsComponentOutputs { + selected: ProfileDto; + form: FormGroup; inProgress: boolean; @@ -28,21 +40,20 @@ export class PersonalSettingsComponent private toasterService: ToasterService, private profileService: ProfileService, private manageProfileState: ManageProfileStateService, + protected injector: Injector, ) {} - ngOnInit() { - this.buildForm(); + buildForm() { + this.selected = this.manageProfileState.getProfile(); + if (!this.selected) { + return; + } + const data = new FormPropData(this.injector, this.selected); + this.form = generateFormFromProps(data); } - buildForm() { - const profile = this.manageProfileState.getProfile(); - this.form = this.fb.group({ - userName: [profile.userName, [required, maxLength(256)]], - email: [profile.email, [required, email, maxLength(256)]], - name: [profile.name || '', [maxLength(64)]], - surname: [profile.surname || '', [maxLength(64)]], - phoneNumber: [profile.phoneNumber || '', [maxLength(16)]], - }); + ngOnInit(): void { + this.buildForm(); } submit() { diff --git a/npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts b/npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts new file mode 100644 index 0000000000..59c196f2bd --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/defaults/default-personal-settings-form-props.ts @@ -0,0 +1,47 @@ +import { ePropType, FormProp } from '@abp/ng.theme.shared/extensions'; +import { UpdateProfileDto } from '@abp/ng.account.core/proxy'; +import { Validators } from '@angular/forms'; +import { PersonalSettingsHalfRowComponent } from '../components/personal-settings/personal-settings-half-row.component'; + +const { maxLength, required, email } = Validators; +export const DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS = FormProp.createMany([ + { + type: ePropType.String, + name: 'userName', + displayName: 'AbpIdentity::DisplayName:UserName', + id: 'username', + validators: () => [required, maxLength(256)], + }, + { + type: ePropType.String, + name: 'name', + displayName: 'AbpIdentity::DisplayName:Name', + id: 'name', + validators: () => [maxLength(64)], + template: PersonalSettingsHalfRowComponent, + className: 'd-inline-block w-50', + }, + { + type: ePropType.String, + name: 'surname', + displayName: 'AbpIdentity::DisplayName:Surname', + id: 'surname', + validators: () => [maxLength(64)], + className: 'd-inline-block w-50 ps-4', + template: PersonalSettingsHalfRowComponent, + }, + { + type: ePropType.String, + name: 'email', + displayName: 'AbpIdentity::DisplayName:Email', + id: 'email-address', + validators: () => [required, email, maxLength(256)], + }, + { + type: ePropType.String, + name: 'phoneNumber', + displayName: 'AbpIdentity::DisplayName:PhoneNumber', + id: 'phone-number', + validators: () => [maxLength(16)], + }, +]); diff --git a/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts b/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts new file mode 100644 index 0000000000..7c50eadf5e --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/guards/extensions.guard.ts @@ -0,0 +1,46 @@ +import { Injectable, Injector } from '@angular/core'; +import { CanActivate } from '@angular/router'; +import { Observable } from 'rxjs'; +import { + ExtensionsService, + getObjectExtensionEntitiesFromStore, + mapEntitiesToContributors, + mergeWithDefaultProps, +} from '@abp/ng.theme.shared/extensions'; +import { ConfigStateService } from '@abp/ng.core'; +import { tap, map, mapTo } from 'rxjs/operators'; +import { + ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, + DEFAULT_ACCOUNT_FORM_PROPS, +} from '../tokens/extensions.token'; +import { AccountEditFormPropContributors } from '../models/config-options'; +import { eAccountComponents } from '../enums/components'; + +@Injectable() +export class AccountExtensionsGuard implements CanActivate { + constructor(private injector: Injector) {} + + canActivate(): Observable { + const extensions: ExtensionsService = this.injector.get(ExtensionsService); + + const editFormContributors: AccountEditFormPropContributors = + this.injector.get(ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS, null) || {}; + + const configState = this.injector.get(ConfigStateService); + return getObjectExtensionEntitiesFromStore(configState, 'Identity').pipe( + map(entities => ({ + [eAccountComponents.PersonalSettings]: entities.User, + })), + mapEntitiesToContributors(configState, 'AbpIdentity'), + tap(objectExtensionContributors => { + mergeWithDefaultProps( + extensions.editFormProps, + DEFAULT_ACCOUNT_FORM_PROPS, + objectExtensionContributors.editForm, + editFormContributors, + ); + }), + mapTo(true), + ); + } +} diff --git a/npm/ng-packs/packages/account/src/lib/guards/index.ts b/npm/ng-packs/packages/account/src/lib/guards/index.ts index 382170b2b1..275cd2f8e8 100644 --- a/npm/ng-packs/packages/account/src/lib/guards/index.ts +++ b/npm/ng-packs/packages/account/src/lib/guards/index.ts @@ -1 +1,2 @@ export * from './authentication-flow.guard'; +export * from './extensions.guard'; diff --git a/npm/ng-packs/packages/account/src/lib/models/config-options.ts b/npm/ng-packs/packages/account/src/lib/models/config-options.ts index 898021b04e..f3af14a71e 100644 --- a/npm/ng-packs/packages/account/src/lib/models/config-options.ts +++ b/npm/ng-packs/packages/account/src/lib/models/config-options.ts @@ -1,3 +1,11 @@ +import { eAccountComponents } from '../enums'; +import { EditFormPropContributorCallback } from '@abp/ng.theme.shared/extensions'; +import { UpdateProfileDto } from '@abp/ng.account.core/proxy'; + export interface AccountConfigOptions { redirectUrl?: string; + editFormPropContributors?: AccountEditFormPropContributors; } +export type AccountEditFormPropContributors = Partial<{ + [eAccountComponents.PersonalSettings]: EditFormPropContributorCallback[]; +}>; \ No newline at end of file diff --git a/npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts b/npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts new file mode 100644 index 0000000000..78acb7feaa --- /dev/null +++ b/npm/ng-packs/packages/account/src/lib/tokens/extensions.token.ts @@ -0,0 +1,17 @@ +import { eAccountComponents } from '../enums'; +import { DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS } from '../defaults/default-personal-settings-form-props'; +import { InjectionToken } from '@angular/core'; +import { EditFormPropContributorCallback } from '@abp/ng.theme.shared/extensions'; +import { UpdateProfileDto } from '@abp/ng.account.core/proxy'; + +export const DEFAULT_ACCOUNT_FORM_PROPS = { + [eAccountComponents.PersonalSettings]: DEFAULT_PERSONAL_SETTINGS_UPDATE_FORM_PROPS, +}; + +export const ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS = new InjectionToken( + 'ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS', +); + +type EditFormPropContributors = Partial<{ + [eAccountComponents.PersonalSettings]: EditFormPropContributorCallback[]; +}>; diff --git a/npm/ng-packs/packages/account/src/lib/tokens/index.ts b/npm/ng-packs/packages/account/src/lib/tokens/index.ts index 0a88318130..4381d1bc77 100644 --- a/npm/ng-packs/packages/account/src/lib/tokens/index.ts +++ b/npm/ng-packs/packages/account/src/lib/tokens/index.ts @@ -1 +1,2 @@ export * from './config-options.token'; +export * from './extensions.token'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/common-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/common-utils.ts index 31a1970fdc..b373b6c04a 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/common-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/common-utils.ts @@ -11,6 +11,10 @@ export function isNullOrUndefined(obj) { return obj === null || obj === undefined; } +export function isNullOrEmpty(obj){ + return obj === null || obj === undefined || obj === ''; +} + export function exists(obj) { return !isNullOrUndefined(obj); } diff --git a/npm/ng-packs/packages/core/src/lib/validators/index.ts b/npm/ng-packs/packages/core/src/lib/validators/index.ts index fb06e7f1d8..69880dbf84 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/index.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/index.ts @@ -5,6 +5,7 @@ import { validateRange } from './range.validator'; import { validateRequired } from './required.validator'; import { validateStringLength } from './string-length.validator'; import { validateUrl } from './url.validator'; +import { validateUsername } from './username.validator'; export * from './age.validator'; export * from './credit-card.validator'; export * from './range.validator'; @@ -20,4 +21,5 @@ export const AbpValidators = { required: validateRequired, stringLength: validateStringLength, url: validateUrl, + username: validateUsername, }; diff --git a/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts index bf2a4b47ee..4e2b1931db 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/required.validator.ts @@ -9,9 +9,11 @@ export interface RequiredOptions { } export function validateRequired({ allowEmptyStrings }: RequiredOptions = {}): ValidatorFn { - return (control: AbstractControl): RequiredError | null => { + // note: please do not remove name of the function, it is used in function compare with type 'RequiredError' + const required = (control: AbstractControl): RequiredError | null => { return isValidRequired(control.value, allowEmptyStrings) ? null : { required: true }; }; + return required; } function isValidRequired(value: any, allowEmptyStrings: boolean): boolean { diff --git a/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts index 705d07e17a..da941aee6f 100644 --- a/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts +++ b/npm/ng-packs/packages/core/src/lib/validators/url.validator.ts @@ -1,4 +1,5 @@ import { AbstractControl, ValidatorFn } from '@angular/forms'; +import { isNullOrUndefined } from '../utils'; export interface UrlError { url: true; @@ -6,11 +7,11 @@ export interface UrlError { export function validateUrl(): ValidatorFn { return (control: AbstractControl): UrlError | null => { - if (['', null, undefined].indexOf(control.value) > -1) return null; + if (isNullOrUndefined(control.value)) return null; return isValidUrl(control.value) ? null : { url: true }; }; -} +} function isValidUrl(value: string): boolean { if (/^http(s)?:\/\/[^/]/.test(value) || /^ftp:\/\/[^/]/.test(value)) { diff --git a/npm/ng-packs/packages/core/src/lib/validators/username.validator.ts b/npm/ng-packs/packages/core/src/lib/validators/username.validator.ts new file mode 100644 index 0000000000..4e943769d2 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/validators/username.validator.ts @@ -0,0 +1,29 @@ +import { AbstractControl, ValidatorFn } from '@angular/forms'; +import { isNullOrEmpty } from '../utils'; + +export interface UsernamePatternError { + usernamePattern: { + actualValue: string; + }; +} + +export interface UsernameOptions { + pattern?: RegExp; +} + +const onlyLetterAndNumberRegex = /^[a-zA-Z0-9]+$/; + +export function validateUsername( + { pattern }: UsernameOptions = { pattern: onlyLetterAndNumberRegex }, +): ValidatorFn { + return (control: AbstractControl): UsernamePatternError | null => { + const isValid = isValidUserName(control.value, pattern); + return isValid ? null : { usernamePattern: { actualValue: control.value } }; + }; +} + +function isValidUserName(value: any, pattern: RegExp): boolean { + if (isNullOrEmpty(value)) return true; + + return pattern.test(value); +} diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 99d435c891..7873adf6ea 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -39,7 +39,7 @@ >
- +