mirror of https://github.com/abpframework/abp.git
46 changed files with 417 additions and 155 deletions
@ -1,40 +1,50 @@ |
|||||
|
import { ABP, SessionSetTenantId } from '@abp/ng.core'; |
||||
|
import { ToasterService } from '@abp/ng.theme.shared'; |
||||
import { Component, TemplateRef, ViewChild } from '@angular/core'; |
import { Component, TemplateRef, ViewChild } from '@angular/core'; |
||||
import { FormBuilder, FormGroup } from '@angular/forms'; |
import { Store } from '@ngxs/store'; |
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; |
import { throwError } from 'rxjs'; |
||||
import { ABP } from '@abp/ng.core'; |
import { catchError, take } from 'rxjs/operators'; |
||||
|
import snq from 'snq'; |
||||
|
import { AccountService } from '../../services/account.service'; |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: 'abp-tenant-box', |
selector: 'abp-tenant-box', |
||||
templateUrl: './tenant-box.component.html', |
templateUrl: './tenant-box.component.html', |
||||
}) |
}) |
||||
export class TenantBoxComponent { |
export class TenantBoxComponent { |
||||
constructor(private modalService: NgbModal, private fb: FormBuilder) {} |
constructor(private store: Store, private toasterService: ToasterService, private accountService: AccountService) {} |
||||
|
|
||||
form: FormGroup; |
selected = {} as ABP.BasicItem; |
||||
|
|
||||
selected: ABP.BasicItem; |
isModalVisible: boolean; |
||||
|
|
||||
@ViewChild('modalContent', { static: false }) |
@ViewChild('modalContent', { static: false }) |
||||
modalContent: TemplateRef<any>; |
modalContent: TemplateRef<any>; |
||||
|
|
||||
createForm() { |
|
||||
this.form = this.fb.group({ |
|
||||
name: [this.selected.name], |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
openModal() { |
|
||||
this.createForm(); |
|
||||
this.modalService.open(this.modalContent); |
|
||||
} |
|
||||
|
|
||||
onSwitch() { |
onSwitch() { |
||||
this.selected = {} as ABP.BasicItem; |
this.isModalVisible = true; |
||||
this.openModal(); |
|
||||
} |
} |
||||
|
|
||||
save() { |
save() { |
||||
this.selected = this.form.value; |
this.selected.name = this.selected.name || ''; |
||||
this.modalService.dismissAll(); |
|
||||
|
this.accountService |
||||
|
.findTenant(this.selected.name) |
||||
|
.pipe( |
||||
|
take(1), |
||||
|
catchError(err => { |
||||
|
this.toasterService.error(snq(() => err.error.error_description, 'An error occured.'), 'Error'); |
||||
|
return throwError(err); |
||||
|
}), |
||||
|
) |
||||
|
.subscribe(({ success, tenantId }) => { |
||||
|
if (success) { |
||||
|
this.isModalVisible = false; |
||||
|
} else { |
||||
|
this.toasterService.error(`Given tenant is not available: ${this.selected.name}`, 'Error'); |
||||
|
this.selected = {} as ABP.BasicItem; |
||||
|
} |
||||
|
this.store.dispatch(new SessionSetTenantId(success ? tenantId : null)); |
||||
|
}); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1 +1,3 @@ |
|||||
export * from './options'; |
export * from './options'; |
||||
|
export * from './user'; |
||||
|
export * from './tenant'; |
||||
|
|||||
@ -0,0 +1,4 @@ |
|||||
|
export interface Tenant { |
||||
|
success: boolean; |
||||
|
tenantId: string; |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
export interface RegisterRequest { |
||||
|
userName: string; |
||||
|
emailAddress: string; |
||||
|
password: string; |
||||
|
appName?: string; |
||||
|
} |
||||
|
|
||||
|
export interface RegisterResponse { |
||||
|
tenantId: string; |
||||
|
userName: string; |
||||
|
name: string; |
||||
|
surname: string; |
||||
|
email: string; |
||||
|
emailConfirmed: boolean; |
||||
|
phoneNumber: string; |
||||
|
phoneNumberConfirmed: boolean; |
||||
|
twoFactorEnabled: boolean; |
||||
|
lockoutEnabled: boolean; |
||||
|
lockoutEnd: string; |
||||
|
concurrencyStamp: string; |
||||
|
isDeleted: boolean; |
||||
|
deleterId: string; |
||||
|
deletionTime: string; |
||||
|
lastModificationTime: string; |
||||
|
lastModifierId: string; |
||||
|
creationTime: string; |
||||
|
creatorId: string; |
||||
|
id: string; |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { RestService, Rest } from '@abp/ng.core'; |
||||
|
import { RegisterResponse, RegisterRequest, Tenant } from '../models'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class AccountService { |
||||
|
constructor(private rest: RestService) {} |
||||
|
|
||||
|
findTenant(tenantName: string): Observable<Tenant> { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'GET', |
||||
|
url: `/api/abp/multi-tenancy/find-tenant/${tenantName}`, |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, Tenant>(request); |
||||
|
} |
||||
|
|
||||
|
register(body: RegisterRequest): Observable<RegisterResponse> { |
||||
|
const request: Rest.Request<RegisterRequest> = { |
||||
|
method: 'POST', |
||||
|
url: `/api/account/register`, |
||||
|
body, |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<RegisterRequest, RegisterResponse>(request, { throwErr: true }); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
import { Directive, ElementRef, Input, AfterViewInit } from '@angular/core'; |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: '[autofocus]', |
||||
|
}) |
||||
|
export class AutofocusDirective implements AfterViewInit { |
||||
|
@Input('autofocus') |
||||
|
delay: number = 0; |
||||
|
|
||||
|
constructor(private elRef: ElementRef) {} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
setTimeout(() => this.elRef.nativeElement.focus(), this.delay); |
||||
|
} |
||||
|
} |
||||
@ -1,3 +1,4 @@ |
|||||
|
export * from './autofocus.directive'; |
||||
export * from './ellipsis.directive'; |
export * from './ellipsis.directive'; |
||||
export * from './permission.directive'; |
export * from './permission.directive'; |
||||
export * from './visibility.directive'; |
export * from './visibility.directive'; |
||||
|
|||||
@ -1,5 +1,6 @@ |
|||||
export namespace Session { |
export namespace Session { |
||||
export interface State { |
export interface State { |
||||
language: string; |
language: string; |
||||
|
tenantId: string; |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1 @@ |
|||||
|
export * from './slide.animation'; |
||||
@ -0,0 +1,6 @@ |
|||||
|
import { animate, state, style, transition, trigger, query } from '@angular/animations'; |
||||
|
export const slideFromBottom = trigger('routeAnimations', [ |
||||
|
state('void', style({ 'margin-top': '20px', opacity: '0' })), |
||||
|
state('*', style({ 'margin-top': '0px', opacity: '1' })), |
||||
|
transition(':enter', [animate('0.2s ease-out', style({ opacity: '1', 'margin-top': '0px' }))]), |
||||
|
]); |
||||
@ -0,0 +1,68 @@ |
|||||
|
import { LoaderStart, LoaderStop } from '@abp/ng.core'; |
||||
|
import { Component, Input, OnInit } from '@angular/core'; |
||||
|
import { Actions, ofActionSuccessful } from '@ngxs/store'; |
||||
|
import { filter } from 'rxjs/operators'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'abp-button', |
||||
|
template: ` |
||||
|
<button [attr.type]="buttonType" [ngClass]="buttonClass" [disabled]="loading"> |
||||
|
<i [ngClass]="icon" class="mr-1"></i><ng-content></ng-content> |
||||
|
</button> |
||||
|
`,
|
||||
|
}) |
||||
|
export class ButtonComponent implements OnInit { |
||||
|
@Input() |
||||
|
buttonClass: string = 'btn btn-primary'; |
||||
|
|
||||
|
@Input() |
||||
|
buttonType: string = 'button'; |
||||
|
|
||||
|
@Input() |
||||
|
iconClass: string; |
||||
|
|
||||
|
@Input() |
||||
|
loading: boolean = false; |
||||
|
|
||||
|
@Input() |
||||
|
requestType: string | string[]; |
||||
|
|
||||
|
@Input() |
||||
|
requestURLContainSearchValue: string; |
||||
|
|
||||
|
get icon(): string { |
||||
|
return `${this.loading ? 'fa fa-spin fa-spinner' : this.iconClass || 'd-none'}`; |
||||
|
} |
||||
|
|
||||
|
constructor(private actions: Actions) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
if (this.requestType || this.requestURLContainSearchValue) { |
||||
|
this.actions |
||||
|
.pipe( |
||||
|
ofActionSuccessful(LoaderStart, LoaderStop), |
||||
|
filter((event: LoaderStart | LoaderStop) => { |
||||
|
let condition = true; |
||||
|
if (this.requestType) { |
||||
|
if (!Array.isArray(this.requestType)) this.requestType = [this.requestType]; |
||||
|
|
||||
|
condition = |
||||
|
condition && |
||||
|
this.requestType.findIndex(type => type.toLowerCase() === event.payload.method.toLowerCase()) > -1; |
||||
|
} |
||||
|
|
||||
|
if (condition && this.requestURLContainSearchValue) { |
||||
|
condition = |
||||
|
condition && |
||||
|
event.payload.url.toLowerCase().indexOf(this.requestURLContainSearchValue.toLowerCase()) > -1; |
||||
|
} |
||||
|
|
||||
|
return condition; |
||||
|
}), |
||||
|
) |
||||
|
.subscribe(() => { |
||||
|
this.loading = !this.loading; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,3 +1,5 @@ |
|||||
|
export * from './button/button.component'; |
||||
export * from './confirmation/confirmation.component'; |
export * from './confirmation/confirmation.component'; |
||||
export * from './toast/toast.component'; |
export * from './loader-bar/loader-bar.component'; |
||||
export * from './modal/modal.component'; |
export * from './modal/modal.component'; |
||||
|
export * from './toast/toast.component'; |
||||
|
|||||
@ -1,10 +1,10 @@ |
|||||
import { Injectable } from '@angular/core'; |
import { Injectable } from '@angular/core'; |
||||
import { AbstractToasterClass } from '../abstracts/toaster'; |
import { AbstractToaster } from '../abstracts/toaster'; |
||||
import { Confirmation } from '../models/confirmation'; |
import { Confirmation } from '../models/confirmation'; |
||||
|
|
||||
@Injectable({ providedIn: 'root' }) |
@Injectable({ providedIn: 'root' }) |
||||
export class ConfirmationService extends AbstractToasterClass<Confirmation.Options> { |
export class ConfirmationService extends AbstractToaster<Confirmation.Options> { |
||||
protected key: string = 'abpConfirmation'; |
key: string = 'abpConfirmation'; |
||||
|
|
||||
protected sticky: boolean = true; |
sticky: boolean = true; |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue