diff --git a/npm/ng-packs/angular.json b/npm/ng-packs/angular.json
index 8aed0992b2..69119cdb64 100644
--- a/npm/ng-packs/angular.json
+++ b/npm/ng-packs/angular.json
@@ -184,6 +184,43 @@
}
}
},
+ "account": {
+ "projectType": "library",
+ "root": "packages/account",
+ "sourceRoot": "packages/account/src",
+ "prefix": "abp",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-ng-packagr:build",
+ "options": {
+ "tsConfig": "packages/account/tsconfig.lib.json",
+ "project": "packages/account/ng-package.json"
+ },
+ "configurations": {
+ "production": {
+ "tsConfig": "packages/account/tsconfig.lib.prod.json"
+ }
+ }
+ },
+ "test": {
+ "builder": "@angular-builders/jest:run",
+ "options": {
+ "coverage": true,
+ "passWithNoTests": true
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "packages/account/tsconfig.lib.json",
+ "packages/account/tsconfig.spec.json"
+ ],
+ "exclude": ["**/node_modules/**"]
+ }
+ }
+ }
+ },
"identity": {
"projectType": "library",
"root": "packages/identity",
diff --git a/npm/ng-packs/packages/account/README.md b/npm/ng-packs/packages/account/README.md
new file mode 100644
index 0000000000..f3eaaed299
--- /dev/null
+++ b/npm/ng-packs/packages/account/README.md
@@ -0,0 +1,3 @@
+
@abp/ng.account
+
+[docs.abp.io](https://docs.abp.io)
diff --git a/npm/ng-packs/packages/account/config/ng-package.json b/npm/ng-packs/packages/account/config/ng-package.json
new file mode 100644
index 0000000000..8bc97815a5
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/account/config",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
diff --git a/npm/ng-packs/packages/account/config/src/account-config.module.ts b/npm/ng-packs/packages/account/config/src/account-config.module.ts
new file mode 100644
index 0000000000..3041e828d7
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/account-config.module.ts
@@ -0,0 +1,22 @@
+import { noop } from '@abp/ng.core';
+import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
+import { SessionHandler } from './handlers/session.handler';
+import { ACCOUNT_ROUTE_PROVIDERS } from './providers/route.provider';
+
+@NgModule()
+export class AccountConfigModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: AccountConfigModule,
+ providers: [
+ ACCOUNT_ROUTE_PROVIDERS,
+ {
+ provide: APP_INITIALIZER,
+ multi: true,
+ deps: [SessionHandler],
+ useFactory: noop,
+ },
+ ],
+ };
+ }
+}
diff --git a/npm/ng-packs/packages/account/config/src/enums/index.ts b/npm/ng-packs/packages/account/config/src/enums/index.ts
new file mode 100644
index 0000000000..3bda94b078
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/enums/index.ts
@@ -0,0 +1 @@
+export * from './route-names';
diff --git a/npm/ng-packs/packages/account/config/src/enums/route-names.ts b/npm/ng-packs/packages/account/config/src/enums/route-names.ts
new file mode 100644
index 0000000000..810985e027
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/enums/route-names.ts
@@ -0,0 +1,6 @@
+export const enum eAccountRouteNames {
+ Account = 'AbpAccount::Menu:Account',
+ Login = 'AbpAccount::Login',
+ Register = 'AbpAccount::Register',
+ ManageProfile = 'AbpAccount::ManageYourProfile',
+}
diff --git a/npm/ng-packs/packages/account/config/src/handlers/index.ts b/npm/ng-packs/packages/account/config/src/handlers/index.ts
new file mode 100644
index 0000000000..3ab2054e88
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/handlers/index.ts
@@ -0,0 +1 @@
+export * from './session.handler';
diff --git a/npm/ng-packs/packages/account/config/src/providers/index.ts b/npm/ng-packs/packages/account/config/src/providers/index.ts
new file mode 100644
index 0000000000..fe08efba8c
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/providers/index.ts
@@ -0,0 +1 @@
+export * from './route.provider';
diff --git a/npm/ng-packs/packages/account/config/src/providers/route.provider.ts b/npm/ng-packs/packages/account/config/src/providers/route.provider.ts
new file mode 100644
index 0000000000..2deb086b10
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/providers/route.provider.ts
@@ -0,0 +1,39 @@
+import { eLayoutType, RoutesService } from '@abp/ng.core';
+import { APP_INITIALIZER } from '@angular/core';
+import { eAccountRouteNames } from '../enums/route-names';
+
+export const ACCOUNT_ROUTE_PROVIDERS = [
+ { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true },
+];
+
+export function configureRoutes(routes: RoutesService) {
+ return () => {
+ routes.add([
+ {
+ path: '/account',
+ name: eAccountRouteNames.Account,
+ invisible: true,
+ layout: eLayoutType.application,
+ order: 1,
+ },
+ {
+ path: '/account/login',
+ name: eAccountRouteNames.Login,
+ parentName: eAccountRouteNames.Account,
+ order: 1,
+ },
+ {
+ path: '/account/register',
+ name: eAccountRouteNames.Register,
+ parentName: eAccountRouteNames.Account,
+ order: 2,
+ },
+ {
+ path: '/account/manage-profile',
+ name: eAccountRouteNames.ManageProfile,
+ parentName: eAccountRouteNames.Account,
+ order: 3,
+ },
+ ]);
+ };
+}
diff --git a/npm/ng-packs/packages/account/config/src/public-api.ts b/npm/ng-packs/packages/account/config/src/public-api.ts
new file mode 100644
index 0000000000..533ef02eb2
--- /dev/null
+++ b/npm/ng-packs/packages/account/config/src/public-api.ts
@@ -0,0 +1,4 @@
+export * from './account-config.module';
+export * from './enums';
+export * from './providers';
+export * from './handlers';
diff --git a/npm/ng-packs/packages/account/jest.config.js b/npm/ng-packs/packages/account/jest.config.js
new file mode 100644
index 0000000000..c9c02c909f
--- /dev/null
+++ b/npm/ng-packs/packages/account/jest.config.js
@@ -0,0 +1,6 @@
+const jestConfig = require('../../jest.config');
+
+module.exports = {
+ ...jestConfig,
+ name: 'account',
+};
diff --git a/npm/ng-packs/packages/account/ng-package.json b/npm/ng-packs/packages/account/ng-package.json
new file mode 100644
index 0000000000..9e94619264
--- /dev/null
+++ b/npm/ng-packs/packages/account/ng-package.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/account",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ },
+ "whitelistedNonPeerDependencies": ["@abp/ng.theme.shared"]
+}
diff --git a/npm/ng-packs/packages/account/ngcc.config.js b/npm/ng-packs/packages/account/ngcc.config.js
new file mode 100644
index 0000000000..e40ad7d892
--- /dev/null
+++ b/npm/ng-packs/packages/account/ngcc.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ entryPoints: {
+ '.': {},
+ './config': {}
+ },
+};
diff --git a/npm/ng-packs/packages/account/package.json b/npm/ng-packs/packages/account/package.json
new file mode 100644
index 0000000000..e667997d9e
--- /dev/null
+++ b/npm/ng-packs/packages/account/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@abp/ng.account",
+ "version": "4.3.0",
+ "homepage": "https://abp.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/abpframework/abp.git"
+ },
+ "dependencies": {
+ "@abp/ng.theme.shared": "~3.3.2",
+ "tslib": "^2.0.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
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
new file mode 100644
index 0000000000..dfc92f41e8
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/account-routing.module.ts
@@ -0,0 +1,93 @@
+import {
+ AuthGuard,
+ DynamicLayoutComponent,
+ ReplaceableComponents,
+ ReplaceableRouteContainerComponent,
+} from '@abp/ng.core';
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
+import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
+import { LoginComponent } from './components/login/login.component';
+import { ManageProfileComponent } from './components/manage-profile/manage-profile.component';
+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';
+
+const routes: Routes = [
+ { path: '', pathMatch: 'full', redirectTo: 'login' },
+ {
+ path: '',
+ component: DynamicLayoutComponent,
+ children: [
+ {
+ path: '',
+ component: AuthWrapperComponent,
+ children: [
+ {
+ path: 'login',
+ component: ReplaceableRouteContainerComponent,
+ canActivate: [AuthenticationFlowGuard],
+ data: {
+ replaceableComponent: {
+ key: eAccountComponents.Login,
+ defaultComponent: LoginComponent,
+ } as ReplaceableComponents.RouteData,
+ },
+ },
+ {
+ path: 'register',
+ component: ReplaceableRouteContainerComponent,
+ canActivate: [AuthenticationFlowGuard],
+ data: {
+ replaceableComponent: {
+ key: eAccountComponents.Register,
+ defaultComponent: RegisterComponent,
+ } as ReplaceableComponents.RouteData,
+ },
+ },
+ {
+ path: 'forgot-password',
+ component: ReplaceableRouteContainerComponent,
+ canActivate: [AuthenticationFlowGuard],
+ data: {
+ replaceableComponent: {
+ key: eAccountComponents.ForgotPassword,
+ defaultComponent: ForgotPasswordComponent,
+ } as ReplaceableComponents.RouteData,
+ },
+ },
+ {
+ path: 'reset-password',
+ component: ReplaceableRouteContainerComponent,
+ canActivate: [AuthenticationFlowGuard],
+ data: {
+ replaceableComponent: {
+ key: eAccountComponents.ResetPassword,
+ defaultComponent: ResetPasswordComponent,
+ } as ReplaceableComponents.RouteData,
+ },
+ },
+ ],
+ },
+ {
+ path: 'manage-profile',
+ component: ReplaceableRouteContainerComponent,
+ canActivate: [AuthGuard],
+ data: {
+ replaceableComponent: {
+ key: eAccountComponents.ManageProfile,
+ defaultComponent: ManageProfileComponent,
+ } as ReplaceableComponents.RouteData,
+ },
+ },
+ ],
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+})
+export class AccountRoutingModule {}
diff --git a/npm/ng-packs/packages/account/src/lib/account.module.ts b/npm/ng-packs/packages/account/src/lib/account.module.ts
new file mode 100644
index 0000000000..255a8b3de5
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/account.module.ts
@@ -0,0 +1,65 @@
+import { CoreModule, LazyModuleFactory } from '@abp/ng.core';
+import { ThemeSharedModule } from '@abp/ng.theme.shared';
+import { ModuleWithProviders, NgModule, NgModuleFactory } from '@angular/core';
+import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
+import { NgxValidateCoreModule } from '@ngx-validate/core';
+import { AccountRoutingModule } from './account-routing.module';
+import { AuthWrapperComponent } from './components/auth-wrapper/auth-wrapper.component';
+import { ChangePasswordComponent } from './components/change-password/change-password.component';
+import { LoginComponent } from './components/login/login.component';
+import { ManageProfileComponent } from './components/manage-profile/manage-profile.component';
+import { PersonalSettingsComponent } from './components/personal-settings/personal-settings.component';
+import { RegisterComponent } from './components/register/register.component';
+import { TenantBoxComponent } from './components/tenant-box/tenant-box.component';
+import { Options } from './models/options';
+import { ACCOUNT_OPTIONS } from './tokens/options.token';
+import { accountOptionsFactory } from './utils/factory-utils';
+import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
+import { AuthenticationService } from './services/authentication.service';
+import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
+import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
+
+const declarations = [
+ AuthWrapperComponent,
+ LoginComponent,
+ RegisterComponent,
+ TenantBoxComponent,
+ ChangePasswordComponent,
+ ManageProfileComponent,
+ PersonalSettingsComponent,
+ ForgotPasswordComponent,
+ ResetPasswordComponent,
+];
+
+@NgModule({
+ declarations: [...declarations],
+ imports: [
+ CoreModule,
+ AccountRoutingModule,
+ ThemeSharedModule,
+ NgbDropdownModule,
+ NgxValidateCoreModule,
+ ],
+ exports: [...declarations],
+})
+export class AccountModule {
+ static forChild(options = {} as Options): ModuleWithProviders {
+ return {
+ ngModule: AccountModule,
+ providers: [
+ AuthenticationFlowGuard,
+ AuthenticationService,
+ { provide: ACCOUNT_OPTIONS, useValue: options },
+ {
+ provide: 'ACCOUNT_OPTIONS',
+ useFactory: accountOptionsFactory,
+ deps: [ACCOUNT_OPTIONS],
+ },
+ ],
+ };
+ }
+
+ static forLazy(options = {} as Options): NgModuleFactory {
+ return new LazyModuleFactory(AccountModule.forChild(options));
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
new file mode 100644
index 0000000000..b932025c3d
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.html
@@ -0,0 +1,25 @@
+
+
+
+
+ {{ 'AbpAccount::InvalidLoginRequest' | abpLocalization }}
+ {{ 'AbpAccount::ThereAreNoLoginSchemesConfiguredForThisClient' | abpLocalization }}
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
new file mode 100644
index 0000000000..245d1c4c25
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/auth-wrapper/auth-wrapper.component.ts
@@ -0,0 +1,28 @@
+import { ConfigStateService, MultiTenancyService, SubscriptionService } from '@abp/ng.core';
+import { Component } from '@angular/core';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { eAccountComponents } from '../../enums/components';
+
+@Component({
+ selector: 'abp-auth-wrapper',
+ templateUrl: './auth-wrapper.component.html',
+ exportAs: 'abpAuthWrapper',
+ providers: [SubscriptionService],
+})
+export class AuthWrapperComponent {
+ isMultiTenancyEnabled$ = this.configState.getDeep$('multiTenancy.isEnabled');
+
+ get enableLocalLogin$(): Observable {
+ return this.configState
+ .getSetting$('Abp.Account.EnableLocalLogin')
+ .pipe(map(value => value?.toLowerCase() !== 'false'));
+ }
+
+ tenantBoxKey = eAccountComponents.TenantBox;
+
+ constructor(
+ public readonly multiTenancy: MultiTenancyService,
+ private configState: ConfigStateService,
+ ) {}
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html
new file mode 100644
index 0000000000..37ac7f0175
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.html
@@ -0,0 +1,48 @@
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
new file mode 100644
index 0000000000..d18bd8b165
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/change-password/change-password.component.ts
@@ -0,0 +1,99 @@
+import { Profile, ProfileService } from '@abp/ng.core';
+import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
+import { Component, Injector, Input, OnInit } from '@angular/core';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { comparePasswords, Validation } from '@ngx-validate/core';
+import { finalize } from 'rxjs/operators';
+import snq from 'snq';
+import { Account } from '../../models/account';
+import { ManageProfileStateService } from '../../services/manage-profile.state.service';
+
+const { required } = Validators;
+
+const PASSWORD_FIELDS = ['newPassword', 'repeatNewPassword'];
+
+@Component({
+ selector: 'abp-change-password-form',
+ templateUrl: './change-password.component.html',
+ exportAs: 'abpChangePasswordForm',
+})
+export class ChangePasswordComponent
+ implements OnInit, Account.ChangePasswordComponentInputs, Account.ChangePasswordComponentOutputs {
+ form: FormGroup;
+
+ inProgress: boolean;
+
+ hideCurrentPassword: boolean;
+
+ mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
+ if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
+
+ return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
+ };
+
+ constructor(
+ private fb: FormBuilder,
+ private injector: Injector,
+ private toasterService: ToasterService,
+ private profileService: ProfileService,
+ private manageProfileState: ManageProfileStateService,
+ ) {}
+
+ ngOnInit(): void {
+ this.hideCurrentPassword = !this.manageProfileState.getProfile()?.hasPassword;
+
+ const passwordValidations = getPasswordValidators(this.injector);
+
+ this.form = this.fb.group(
+ {
+ password: ['', required],
+ newPassword: [
+ '',
+ {
+ validators: [required, ...passwordValidations],
+ },
+ ],
+ repeatNewPassword: [
+ '',
+ {
+ validators: [required, ...passwordValidations],
+ },
+ ],
+ },
+ {
+ validators: [comparePasswords(PASSWORD_FIELDS)],
+ },
+ );
+
+ if (this.hideCurrentPassword) this.form.removeControl('password');
+ }
+
+ onSubmit() {
+ if (this.form.invalid) return;
+ this.inProgress = true;
+ this.profileService
+ .changePassword({
+ ...(!this.hideCurrentPassword && { currentPassword: this.form.get('password').value }),
+ newPassword: this.form.get('newPassword').value,
+ })
+ .pipe(finalize(() => (this.inProgress = false)))
+ .subscribe({
+ next: () => {
+ this.form.reset();
+ this.toasterService.success('AbpAccount::PasswordChangedMessage', '', {
+ life: 5000,
+ });
+
+ if (this.hideCurrentPassword) {
+ this.hideCurrentPassword = false;
+ this.form.addControl('password', new FormControl('', [required]));
+ }
+ },
+ error: err => {
+ this.toasterService.error(
+ snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
+ );
+ },
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.html b/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.html
new file mode 100644
index 0000000000..9e1b974c43
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.html
@@ -0,0 +1,40 @@
+{{ 'AbpAccount::ForgotPassword' | abpLocalization }}
+
+
+
+
+
+ {{ 'AbpAccount::PasswordResetMailSentMessage' | abpLocalization }}
+
+
+
+
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts
new file mode 100644
index 0000000000..8ba5b0c991
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/forgot-password/forgot-password.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { finalize } from 'rxjs/operators';
+import { AccountService } from '../../proxy/account/account.service';
+
+@Component({
+ selector: 'abp-forgot-password',
+ templateUrl: 'forgot-password.component.html',
+})
+export class ForgotPasswordComponent {
+ form: FormGroup;
+
+ inProgress: boolean;
+
+ isEmailSent: boolean = false;
+
+ constructor(private fb: FormBuilder, private accountService: AccountService) {
+ this.form = this.fb.group({
+ email: ['', [Validators.required, Validators.email]],
+ });
+ }
+
+ onSubmit() {
+ if (this.form.invalid) return;
+
+ this.inProgress = true;
+
+ this.accountService
+ .sendPasswordResetCode({ email: this.form.get('email').value, appName: 'Angular' })
+ .pipe(finalize(() => (this.inProgress = false)))
+ .subscribe(() => {
+ this.isEmailSent = true;
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/index.ts b/npm/ng-packs/packages/account/src/lib/components/index.ts
new file mode 100644
index 0000000000..458f0623a6
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/index.ts
@@ -0,0 +1,8 @@
+export * from './change-password/change-password.component';
+export * from './forgot-password/forgot-password.component';
+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 './reset-password/reset-password.component';
+export * from './tenant-box/tenant-box.component';
diff --git a/npm/ng-packs/packages/account/src/lib/components/login/login.component.html b/npm/ng-packs/packages/account/src/lib/components/login/login.component.html
new file mode 100644
index 0000000000..061d182ccf
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/login/login.component.html
@@ -0,0 +1,60 @@
+{{ 'AbpAccount::Login' | abpLocalization }}
+
+ {{ 'AbpAccount::AreYouANewUser' | abpLocalization }}
+ {{
+ 'AbpAccount::Register' | abpLocalization
+ }}
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts b/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
new file mode 100644
index 0000000000..fd45e0cdbd
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/login/login.component.ts
@@ -0,0 +1,80 @@
+import { ConfigStateService } from '@abp/ng.core';
+import { ToasterService } from '@abp/ng.theme.shared';
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Store } from '@ngxs/store';
+import { throwError } from 'rxjs';
+import { catchError, finalize } from 'rxjs/operators';
+import snq from 'snq';
+import { eAccountComponents } from '../../enums/components';
+import { AuthenticationService } from '../../services/authentication.service';
+
+const { maxLength, minLength, required } = Validators;
+
+@Component({
+ selector: 'abp-login',
+ templateUrl: './login.component.html',
+})
+export class LoginComponent implements OnInit {
+ form: FormGroup;
+
+ inProgress: boolean;
+
+ isSelfRegistrationEnabled = true;
+
+ authWrapperKey = eAccountComponents.AuthWrapper;
+
+ constructor(
+ private fb: FormBuilder,
+ private store: Store,
+ private toasterService: ToasterService,
+ private authenticationService: AuthenticationService,
+ private configState: ConfigStateService,
+ ) {}
+
+ ngOnInit() {
+ this.init();
+ this.buildForm();
+ }
+
+ protected init() {
+ this.isSelfRegistrationEnabled =
+ (
+ (this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') as string) || ''
+ ).toLowerCase() !== 'false';
+ }
+
+ protected buildForm() {
+ this.form = this.fb.group({
+ username: ['', [required, maxLength(255)]],
+ password: ['', [required, maxLength(128)]],
+ remember: [false],
+ });
+ }
+
+ onSubmit() {
+ if (this.form.invalid) return;
+
+ this.inProgress = true;
+
+ const { username, password, remember } = this.form.value;
+
+ this.authenticationService
+ .login(username, password, remember)
+ .pipe(
+ catchError(err => {
+ this.toasterService.error(
+ snq(() => err.error.error_description) ||
+ snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
+ 'Error',
+ { life: 7000 },
+ );
+ return throwError(err);
+ }),
+ finalize(() => (this.inProgress = false)),
+ )
+ .subscribe(() => {
+ // TODO: this.store.dispatch(new SetRemember(this.form.get('remember').value));
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html
new file mode 100644
index 0000000000..91b635c080
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+ {{ 'AbpIdentity::ChangePassword' | abpLocalization }}
+
+
+
+
+
+
+
+
+ {{ 'AbpIdentity::PersonalSettings' | abpLocalization }}
+
+
+
+
+
+
+
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
new file mode 100644
index 0000000000..695bec8d68
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/manage-profile/manage-profile.component.ts
@@ -0,0 +1,45 @@
+import { Profile, ProfileService } from '@abp/ng.core';
+import { fadeIn } from '@abp/ng.theme.shared';
+import { transition, trigger, useAnimation } from '@angular/animations';
+import { Component, OnInit } from '@angular/core';
+import { eAccountComponents } from '../../enums/components';
+import { ManageProfileStateService } from '../../services/manage-profile.state.service';
+
+@Component({
+ selector: 'abp-manage-profile',
+ templateUrl: './manage-profile.component.html',
+ animations: [trigger('fadeIn', [transition(':enter', useAnimation(fadeIn))])],
+ styles: [
+ `
+ .min-h-400 {
+ min-height: 400px;
+ }
+ `,
+ ],
+})
+export class ManageProfileComponent implements OnInit {
+ selectedTab = 0;
+
+ changePasswordKey = eAccountComponents.ChangePassword;
+
+ personalSettingsKey = eAccountComponents.PersonalSettings;
+
+ profile$ = this.manageProfileState.getProfile$();
+
+ hideChangePasswordTab: boolean;
+
+ constructor(
+ protected profileService: ProfileService,
+ protected manageProfileState: ManageProfileStateService,
+ ) {}
+
+ ngOnInit() {
+ this.profileService.get().subscribe(profile => {
+ this.manageProfileState.setProfile(profile);
+ if (profile.isExternal) {
+ this.hideChangePasswordTab = true;
+ this.selectedTab = 1;
+ }
+ });
+ }
+}
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
new file mode 100644
index 0000000000..291d3f1cbd
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.html
@@ -0,0 +1,46 @@
+
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
new file mode 100644
index 0000000000..2330fe54b3
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
@@ -0,0 +1,60 @@
+import { Profile, ProfileService } from '@abp/ng.core';
+import { ToasterService } from '@abp/ng.theme.shared';
+import { Component, Input, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { Store } from '@ngxs/store';
+import { finalize } from 'rxjs/operators';
+import { Account } from '../../models/account';
+import { ManageProfileStateService } from '../../services/manage-profile.state.service';
+
+const { maxLength, required, email } = Validators;
+
+@Component({
+ selector: 'abp-personal-settings-form',
+ templateUrl: './personal-settings.component.html',
+ exportAs: 'abpPersonalSettingsForm',
+})
+export class PersonalSettingsComponent
+ implements
+ OnInit,
+ Account.PersonalSettingsComponentInputs,
+ Account.PersonalSettingsComponentOutputs {
+ form: FormGroup;
+
+ inProgress: boolean;
+
+ constructor(
+ private fb: FormBuilder,
+ private store: Store,
+ private toasterService: ToasterService,
+ private profileService: ProfileService,
+ private manageProfileState: ManageProfileStateService,
+ ) {}
+
+ ngOnInit() {
+ this.buildForm();
+ }
+
+ 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)]],
+ });
+ }
+
+ submit() {
+ if (this.form.invalid) return;
+ this.inProgress = true;
+ this.profileService
+ .update(this.form.value)
+ .pipe(finalize(() => (this.inProgress = false)))
+ .subscribe(profile => {
+ this.manageProfileState.setProfile(profile);
+ this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/register/register.component.html b/npm/ng-packs/packages/account/src/lib/components/register/register.component.html
new file mode 100644
index 0000000000..404c453026
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/register/register.component.html
@@ -0,0 +1,51 @@
+{{ 'AbpAccount::Register' | abpLocalization }}
+
+ {{ 'AbpAccount::AlreadyRegistered' | abpLocalization }}
+ {{
+ 'AbpAccount::Login' | abpLocalization
+ }}
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
new file mode 100644
index 0000000000..63454c3233
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/register/register.component.ts
@@ -0,0 +1,96 @@
+import { ConfigStateService } from '@abp/ng.core';
+import { getPasswordValidators, ToasterService } from '@abp/ng.theme.shared';
+import { Component, Injector, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { throwError } from 'rxjs';
+import { catchError, finalize, switchMap } from 'rxjs/operators';
+import snq from 'snq';
+import { eAccountComponents } from '../../enums/components';
+import { AccountService } from '../../proxy/account/account.service';
+import { RegisterDto } from '../../proxy/account/models';
+import { AuthenticationService } from '../../services';
+const { maxLength, required, email } = Validators;
+
+@Component({
+ selector: 'abp-register',
+ templateUrl: './register.component.html',
+})
+export class RegisterComponent implements OnInit {
+ form: FormGroup;
+
+ inProgress: boolean;
+
+ isSelfRegistrationEnabled = true;
+
+ authWrapperKey = eAccountComponents.AuthWrapper;
+
+ constructor(
+ private fb: FormBuilder,
+ private accountService: AccountService,
+ private configState: ConfigStateService,
+ private toasterService: ToasterService,
+ private authenticationSerivice: AuthenticationService,
+ private injector: Injector,
+ ) {}
+
+ ngOnInit() {
+ this.init();
+ this.buildForm();
+ }
+
+ protected init() {
+ this.isSelfRegistrationEnabled =
+ (this.configState.getSetting('Abp.Account.IsSelfRegistrationEnabled') || '').toLowerCase() !==
+ 'false';
+
+ if (!this.isSelfRegistrationEnabled) {
+ this.toasterService.warn(
+ {
+ key: 'AbpAccount::SelfRegistrationDisabledMessage',
+ defaultValue: 'Self registration is disabled.',
+ },
+ null,
+ { life: 10000 },
+ );
+ return;
+ }
+ }
+
+ protected buildForm() {
+ this.form = this.fb.group({
+ username: ['', [required, maxLength(255)]],
+ password: ['', [required, ...getPasswordValidators(this.injector)]],
+ email: ['', [required, email]],
+ });
+ }
+
+ onSubmit() {
+ if (this.form.invalid) return;
+
+ this.inProgress = true;
+
+ const newUser = {
+ userName: this.form.get('username').value,
+ password: this.form.get('password').value,
+ emailAddress: this.form.get('email').value,
+ appName: 'Angular',
+ } as RegisterDto;
+
+ this.accountService
+ .register(newUser)
+ .pipe(
+ switchMap(() => this.authenticationSerivice.login(newUser.userName, newUser.password)),
+ catchError(err => {
+ this.toasterService.error(
+ snq(() => err.error.error_description) ||
+ snq(() => err.error.error.message, 'AbpAccount::DefaultErrorMessage'),
+ 'Error',
+ { life: 7000 },
+ );
+ return throwError(err);
+ }),
+ finalize(() => (this.inProgress = false)),
+ )
+ .subscribe();
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.html b/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.html
new file mode 100644
index 0000000000..404f525111
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.html
@@ -0,0 +1,49 @@
+{{ 'AbpAccount::ResetPassword' | abpLocalization }}
+
+
+
+
+
+ {{ 'AbpAccount::YourPasswordIsSuccessfullyReset' | abpLocalization }}
+
+
+
+
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts b/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
new file mode 100644
index 0000000000..c6b0a669bc
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/reset-password/reset-password.component.ts
@@ -0,0 +1,70 @@
+import { getPasswordValidators } from '@abp/ng.theme.shared';
+import { Component, Injector, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+import { comparePasswords, Validation } from '@ngx-validate/core';
+import { finalize } from 'rxjs/operators';
+import { AccountService } from '../../proxy/account/account.service';
+
+const PASSWORD_FIELDS = ['password', 'confirmPassword'];
+
+@Component({
+ selector: 'abp-reset-password',
+ templateUrl: './reset-password.component.html',
+})
+export class ResetPasswordComponent implements OnInit {
+ form: FormGroup;
+
+ inProgress: boolean = false;
+
+ isPasswordReset: boolean = false;
+
+ mapErrorsFn: Validation.MapErrorsFn = (errors, groupErrors, control) => {
+ if (PASSWORD_FIELDS.indexOf(String(control.name)) < 0) return errors;
+
+ return errors.concat(groupErrors.filter(({ key }) => key === 'passwordMismatch'));
+ };
+
+ constructor(
+ private fb: FormBuilder,
+ private accountService: AccountService,
+ private route: ActivatedRoute,
+ private router: Router,
+ private injector: Injector,
+ ) {}
+
+ ngOnInit(): void {
+ this.route.queryParams.subscribe(({ userId, resetToken }) => {
+ if (!userId || !resetToken) this.router.navigateByUrl('/account/login');
+
+ this.form = this.fb.group(
+ {
+ userId: [userId, [Validators.required]],
+ resetToken: [resetToken, [Validators.required]],
+ password: ['', [Validators.required, ...getPasswordValidators(this.injector)]],
+ confirmPassword: ['', [Validators.required, ...getPasswordValidators(this.injector)]],
+ },
+ {
+ validators: [comparePasswords(PASSWORD_FIELDS)],
+ },
+ );
+ });
+ }
+
+ onSubmit() {
+ if (this.form.invalid || this.inProgress) return;
+
+ this.inProgress = true;
+
+ this.accountService
+ .resetPassword({
+ userId: this.form.get('userId').value,
+ resetToken: this.form.get('resetToken').value,
+ password: this.form.get('password').value,
+ })
+ .pipe(finalize(() => (this.inProgress = false)))
+ .subscribe(() => {
+ this.isPasswordReset = true;
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.html b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.html
new file mode 100644
index 0000000000..e8b2df61e1
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+ {{
+ 'AbpUiMultiTenancy::Tenant' | abpLocalization
+ }}
+
+ {{ currentTenant.name || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}
+
+
+
+
+
+
+
+
+
+ Switch Tenant
+
+
+
+
+
+
+
+ {{ 'AbpTenantManagement::Save' | abpLocalization }}
+
+
+
+
diff --git a/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts
new file mode 100644
index 0000000000..a1bc72c29e
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/components/tenant-box/tenant-box.component.ts
@@ -0,0 +1,75 @@
+import {
+ AbpApplicationConfigurationService,
+ AbpTenantService,
+ ConfigStateService,
+ CurrentTenantDto,
+ SessionStateService,
+} from '@abp/ng.core';
+import { ToasterService } from '@abp/ng.theme.shared';
+import { Component } from '@angular/core';
+import { Store } from '@ngxs/store';
+import { finalize } from 'rxjs/operators';
+import { Account } from '../../models/account';
+
+@Component({
+ selector: 'abp-tenant-box',
+ templateUrl: './tenant-box.component.html',
+})
+export class TenantBoxComponent
+ implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
+ currentTenant$ = this.sessionState.getTenant$();
+
+ name: string;
+
+ isModalVisible: boolean;
+
+ modalBusy: boolean;
+
+ constructor(
+ private store: Store,
+ private toasterService: ToasterService,
+ private tenantService: AbpTenantService,
+ private sessionState: SessionStateService,
+ private configState: ConfigStateService,
+ private appConfigService: AbpApplicationConfigurationService,
+ ) {}
+
+ onSwitch() {
+ const tenant = this.sessionState.getTenant();
+ this.name = tenant?.name;
+ this.isModalVisible = true;
+ }
+
+ save() {
+ if (!this.name) {
+ this.setTenant(null);
+ this.isModalVisible = false;
+ return;
+ }
+
+ this.modalBusy = true;
+ this.tenantService
+ .findTenantByName(this.name, {})
+ .pipe(finalize(() => (this.modalBusy = false)))
+ .subscribe(({ success, ...tenant }) => {
+ if (!success) {
+ this.showError();
+ return;
+ }
+
+ this.setTenant({ ...tenant, isAvailable: true });
+ this.isModalVisible = false;
+ });
+ }
+
+ private setTenant(tenant: CurrentTenantDto) {
+ this.sessionState.setTenant(tenant);
+ this.appConfigService.get().subscribe(res => this.configState.setState(res));
+ }
+
+ private showError() {
+ this.toasterService.error('AbpUiMultiTenancy::GivenTenantIsNotAvailable', 'AbpUi::Error', {
+ messageLocalizationParams: [this.name],
+ });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/enums/components.ts b/npm/ng-packs/packages/account/src/lib/enums/components.ts
new file mode 100644
index 0000000000..cebfccc13e
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/enums/components.ts
@@ -0,0 +1,11 @@
+export const enum eAccountComponents {
+ Login = 'Account.LoginComponent',
+ Register = 'Account.RegisterComponent',
+ ForgotPassword = 'Account.ForgotPasswordComponent',
+ ResetPassword = 'Account.ResetPasswordComponent',
+ ManageProfile = 'Account.ManageProfileComponent',
+ TenantBox = 'Account.TenantBoxComponent',
+ AuthWrapper = 'Account.AuthWrapperComponent',
+ ChangePassword = 'Account.ChangePasswordComponent',
+ PersonalSettings = 'Account.PersonalSettingsComponent',
+}
diff --git a/npm/ng-packs/packages/account/src/lib/enums/index.ts b/npm/ng-packs/packages/account/src/lib/enums/index.ts
new file mode 100644
index 0000000000..07635cbbc8
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/enums/index.ts
@@ -0,0 +1 @@
+export * from './components';
diff --git a/npm/ng-packs/packages/account/src/lib/guards/authentication-flow.guard.ts b/npm/ng-packs/packages/account/src/lib/guards/authentication-flow.guard.ts
new file mode 100644
index 0000000000..7cb719628e
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/guards/authentication-flow.guard.ts
@@ -0,0 +1,15 @@
+import { AuthService } from '@abp/ng.core';
+import { Injectable } from '@angular/core';
+import { CanActivate } from '@angular/router';
+
+@Injectable()
+export class AuthenticationFlowGuard implements CanActivate {
+ constructor(private authService: AuthService) {}
+
+ canActivate() {
+ if (this.authService.isInternalAuth) return true;
+
+ this.authService.initLogin();
+ return false;
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/guards/index.ts b/npm/ng-packs/packages/account/src/lib/guards/index.ts
new file mode 100644
index 0000000000..382170b2b1
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/guards/index.ts
@@ -0,0 +1 @@
+export * from './authentication-flow.guard';
diff --git a/npm/ng-packs/packages/account/src/lib/models/account.ts b/npm/ng-packs/packages/account/src/lib/models/account.ts
new file mode 100644
index 0000000000..ef5ad926a2
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/models/account.ts
@@ -0,0 +1,12 @@
+import { TemplateRef } from '@angular/core';
+
+export namespace Account {
+ //tslint:disable
+ export interface TenantBoxComponentInputs {}
+ export interface TenantBoxComponentOutputs {}
+ export interface PersonalSettingsComponentInputs {}
+ export interface PersonalSettingsComponentOutputs {}
+ export interface ChangePasswordComponentInputs {}
+ export interface ChangePasswordComponentOutputs {}
+ // tslint:enable
+}
diff --git a/npm/ng-packs/packages/account/src/lib/models/index.ts b/npm/ng-packs/packages/account/src/lib/models/index.ts
new file mode 100644
index 0000000000..73cf4bcd64
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/models/index.ts
@@ -0,0 +1,2 @@
+export * from './account';
+export * from './options';
diff --git a/npm/ng-packs/packages/account/src/lib/models/options.ts b/npm/ng-packs/packages/account/src/lib/models/options.ts
new file mode 100644
index 0000000000..f07ccb8900
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/models/options.ts
@@ -0,0 +1,3 @@
+export interface Options {
+ redirectUrl?: string;
+}
diff --git a/npm/ng-packs/packages/account/src/lib/services/authentication.service.ts b/npm/ng-packs/packages/account/src/lib/services/authentication.service.ts
new file mode 100644
index 0000000000..2cd303f373
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/services/authentication.service.ts
@@ -0,0 +1,55 @@
+import { Inject, Injectable, Optional } from '@angular/core';
+import {
+ AbpApplicationConfigurationService,
+ ConfigStateService,
+ RestService,
+ SessionStateService,
+} from '@abp/ng.core';
+import { from, Observable } from 'rxjs';
+import { OAuthService } from 'angular-oauth2-oidc';
+import { HttpHeaders } from '@angular/common/http';
+import { switchMap, take, tap } from 'rxjs/operators';
+import { ACCOUNT_OPTIONS } from '../tokens/options.token';
+import { Options } from '../models/options';
+import snq from 'snq';
+import { Router } from '@angular/router';
+import { SessionHandler } from '@abp/ng.account/config';
+
+@Injectable()
+export class AuthenticationService {
+ constructor(
+ protected rest: RestService,
+ protected sessionState: SessionStateService,
+ protected oAuthService: OAuthService,
+ protected appConfigService: AbpApplicationConfigurationService,
+ protected configState: ConfigStateService,
+ @Inject(ACCOUNT_OPTIONS) protected options: Options,
+ protected router: Router,
+ @Optional() protected sessionHandler: SessionHandler,
+ ) {}
+
+ login(username: string, password: string, remember = false): Observable {
+ const tenant = this.sessionState.getTenant();
+
+ return from(
+ this.oAuthService.fetchTokenUsingPasswordFlow(
+ username,
+ password,
+ new HttpHeaders({ ...(tenant && tenant.id && { __tenant: tenant.id }) }),
+ ),
+ ).pipe(
+ switchMap(() => this.appConfigService.get()),
+ tap(res => {
+ this.configState.setState(res);
+
+ const redirectUrl =
+ snq(() => window.history.state.redirectUrl) || (this.options || {}).redirectUrl || '/';
+
+ this.router.navigate([redirectUrl]);
+
+ if (this.sessionHandler) this.sessionHandler.setRememberInfo(remember);
+ }),
+ take(1),
+ );
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/services/index.ts b/npm/ng-packs/packages/account/src/lib/services/index.ts
new file mode 100644
index 0000000000..12e6331345
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/services/index.ts
@@ -0,0 +1,2 @@
+export * from './authentication.service';
+export * from './manage-profile.state.service';
diff --git a/npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts b/npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts
new file mode 100644
index 0000000000..74c58c0674
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/services/manage-profile.state.service.ts
@@ -0,0 +1,28 @@
+import { Injectable } from '@angular/core';
+import { InternalStore, Profile } from '@abp/ng.core';
+import { Observable } from 'rxjs';
+
+export interface ManageProfileState {
+ profile: Profile.Response;
+}
+
+@Injectable({ providedIn: 'root' })
+export class ManageProfileStateService {
+ private readonly store = new InternalStore({} as ManageProfileState);
+
+ get createOnUpdateStream() {
+ return this.store.sliceUpdate;
+ }
+
+ getProfile$(): Observable {
+ return this.store.sliceState(state => state.profile);
+ }
+
+ getProfile(): Profile.Response {
+ return this.store.state.profile;
+ }
+
+ setProfile(profile: Profile.Response) {
+ this.store.patch({ profile });
+ }
+}
diff --git a/npm/ng-packs/packages/account/src/lib/tokens/index.ts b/npm/ng-packs/packages/account/src/lib/tokens/index.ts
new file mode 100644
index 0000000000..683bc4b3db
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/tokens/index.ts
@@ -0,0 +1 @@
+export * from './options.token';
diff --git a/npm/ng-packs/packages/account/src/lib/tokens/options.token.ts b/npm/ng-packs/packages/account/src/lib/tokens/options.token.ts
new file mode 100644
index 0000000000..a7f29c43b9
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/tokens/options.token.ts
@@ -0,0 +1,4 @@
+import { InjectionToken } from '@angular/core';
+import { Options } from '../models/options';
+
+export const ACCOUNT_OPTIONS = new InjectionToken('ACCOUNT_OPTIONS');
diff --git a/npm/ng-packs/packages/account/src/lib/utils/factory-utils.ts b/npm/ng-packs/packages/account/src/lib/utils/factory-utils.ts
new file mode 100644
index 0000000000..3e7c88f795
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/utils/factory-utils.ts
@@ -0,0 +1,8 @@
+import { Options } from '../models/options';
+
+export function accountOptionsFactory(options: Options) {
+ return {
+ redirectUrl: '/',
+ ...options,
+ };
+}
diff --git a/npm/ng-packs/packages/account/src/lib/utils/index.ts b/npm/ng-packs/packages/account/src/lib/utils/index.ts
new file mode 100644
index 0000000000..ed99d0aaa4
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/lib/utils/index.ts
@@ -0,0 +1 @@
+export * from './factory-utils';
diff --git a/npm/ng-packs/packages/account/src/public-api.ts b/npm/ng-packs/packages/account/src/public-api.ts
new file mode 100644
index 0000000000..c3e80345c5
--- /dev/null
+++ b/npm/ng-packs/packages/account/src/public-api.ts
@@ -0,0 +1,9 @@
+export * from './lib/account.module';
+export * from './lib/components';
+export * from './lib/enums';
+export * from './lib/guards';
+export * from './lib/models';
+export * from './lib/services';
+export * from './lib/tokens';
+export * from './lib/proxy/account';
+export * from './lib/proxy/identity';
diff --git a/npm/ng-packs/packages/account/tsconfig.lib.json b/npm/ng-packs/packages/account/tsconfig.lib.json
new file mode 100644
index 0000000000..4f48db2e46
--- /dev/null
+++ b/npm/ng-packs/packages/account/tsconfig.lib.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../tsconfig.prod.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": ["dom", "es2018"]
+ },
+ "angularCompilerOptions": {
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true
+ },
+ "exclude": ["src/test.ts", "**/*.spec.ts"]
+}
diff --git a/npm/ng-packs/packages/account/tsconfig.lib.prod.json b/npm/ng-packs/packages/account/tsconfig.lib.prod.json
new file mode 100644
index 0000000000..b1d501abbc
--- /dev/null
+++ b/npm/ng-packs/packages/account/tsconfig.lib.prod.json
@@ -0,0 +1,6 @@
+{
+ "extends": "./tsconfig.lib.json",
+ "angularCompilerOptions": {
+ "enableIvy": false
+ }
+}
\ No newline at end of file
diff --git a/npm/ng-packs/packages/account/tsconfig.spec.json b/npm/ng-packs/packages/account/tsconfig.spec.json
new file mode 100644
index 0000000000..02f312c262
--- /dev/null
+++ b/npm/ng-packs/packages/account/tsconfig.spec.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.prod.json",
+ "compilerOptions": {
+ "emitDecoratorMetadata": true,
+ "esModuleInterop": true,
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "include": ["**/*.spec.ts", "**/*.d.ts"]
+}
diff --git a/npm/ng-packs/packages/account/tslint.json b/npm/ng-packs/packages/account/tslint.json
new file mode 100644
index 0000000000..8c3919ea62
--- /dev/null
+++ b/npm/ng-packs/packages/account/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "abp",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "abp",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/npm/ng-packs/tsconfig.json b/npm/ng-packs/tsconfig.json
index c67c628f64..b35406a4db 100644
--- a/npm/ng-packs/tsconfig.json
+++ b/npm/ng-packs/tsconfig.json
@@ -22,6 +22,8 @@
"@abp/ng.theme.shared/extensions": ["packages/theme-shared/extensions/src/public-api.ts"],
"@abp/ng.components/tree": ["packages/components/tree/src/public-api.ts"],
"@abp/ng.theme.basic": ["packages/theme-basic/src/public-api.ts"],
+ "@abp/ng.account": ["packages/account/src/public-api.ts"],
+ "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"],
"@abp/ng.identity": ["packages/identity/src/public-api.ts"],
"@abp/ng.identity/config": ["packages/identity/config/src/public-api.ts"],
"@abp/ng.tenant-management": ["packages/tenant-management/src/public-api.ts"],