SKE-67 SKE-68 user profile

This commit is contained in:
Przemysław Stawujak 2019-01-13 16:48:35 +01:00
parent 7cf0245b43
commit 5565bb7070
14 changed files with 449 additions and 4 deletions

View File

@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AppRouteGuard } from '@shared/auth/auth-route-guard'; import { AppRouteGuard } from '@shared/auth/auth-route-guard';
import { HomeComponent } from './home/home.component'; import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';
import { AboutComponent } from './about/about.component'; import { AboutComponent } from './about/about.component';
import { UsersComponent } from './users/users.component'; import { UsersComponent } from './users/users.component';
import { TenantsComponent } from './tenants/tenants.component'; import { TenantsComponent } from './tenants/tenants.component';
@ -20,6 +21,7 @@ import { CompetitionCreateComponent } from '@app/competition-create/competition-
component: AppComponent, component: AppComponent,
children: [ children: [
{ path: 'home', component: HomeComponent, canActivate: [AppRouteGuard] }, { path: 'home', component: HomeComponent, canActivate: [AppRouteGuard] },
{ path: 'profile', component: ProfileComponent, canActivate: [AppRouteGuard] },
{ path: 'users', component: UsersComponent, data: { permission: 'Pages.Users' }, canActivate: [AppRouteGuard] }, { path: 'users', component: UsersComponent, data: { permission: 'Pages.Users' }, canActivate: [AppRouteGuard] },
{ path: 'roles', component: RolesComponent, data: { permission: 'Pages.Roles' }, canActivate: [AppRouteGuard] }, { path: 'roles', component: RolesComponent, data: { permission: 'Pages.Roles' }, canActivate: [AppRouteGuard] },
{ path: 'tenants', component: TenantsComponent, data: { permission: 'Pages.Tenants' }, canActivate: [AppRouteGuard] }, { path: 'tenants', component: TenantsComponent, data: { permission: 'Pages.Tenants' }, canActivate: [AppRouteGuard] },

View File

@ -16,6 +16,7 @@ import { ServiceProxyModule } from '@shared/service-proxies/service-proxy.module
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import { HomeComponent } from '@app/home/home.component'; import { HomeComponent } from '@app/home/home.component';
import { ProfileComponent } from '@app/profile/profile.component';
import { AboutComponent } from '@app/about/about.component'; import { AboutComponent } from '@app/about/about.component';
import { UsersComponent } from '@app/users/users.component'; import { UsersComponent } from '@app/users/users.component';
import { CreateUserComponent } from '@app/users/create-user/create-user.component'; import { CreateUserComponent } from '@app/users/create-user/create-user.component';
@ -54,6 +55,7 @@ import {
declarations: [ declarations: [
AppComponent, AppComponent,
HomeComponent, HomeComponent,
ProfileComponent,
AboutComponent, AboutComponent,
TenantsComponent, TenantsComponent,
CreateTenantComponent, CreateTenantComponent,

View File

@ -11,6 +11,7 @@ export class SideBarNavComponent extends AppComponentBase {
menuItems: MenuItem[] = [ menuItems: MenuItem[] = [
new MenuItem(this.l("Strona domowa"), "", "home", "/app/home"), new MenuItem(this.l("Strona domowa"), "", "home", "/app/home"),
new MenuItem(this.l("Profil"), "", "person", "/app/profile"),
new MenuItem(this.l("Konkursy"), "", "list", "/app/categories-list"), new MenuItem(this.l("Konkursy"), "", "list", "/app/categories-list"),
new MenuItem(this.l("Dodaj konkurs"), "Pages.Create.Competition", "add", "/app/competition-create"), new MenuItem(this.l("Dodaj konkurs"), "Pages.Create.Competition", "add", "/app/competition-create"),

View File

@ -2,7 +2,7 @@
<div class="image"> <div class="image">
<img src="assets/images/user.png" width="48" height="48" alt="User" /> <img src="assets/images/user.png" width="48" height="48" alt="User" />
</div> </div>
<div class="info-container"> <!-- <div class="info-container">
<div class="name" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{shownLoginName}}</div> <div class="name" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{shownLoginName}}</div>
<div class="email">{{appSession.user.emailAddress}}</div> <div class="email">{{appSession.user.emailAddress}}</div>
<div class="btn-group user-helper-dropdown"> <div class="btn-group user-helper-dropdown">
@ -11,5 +11,5 @@
<li><a (click)="logout()"><i class="material-icons">input</i>{{l('Wyloguj')}}</a></li> <li><a (click)="logout()"><i class="material-icons">input</i>{{l('Wyloguj')}}</a></li>
</ul> </ul>
</div> </div>
</div> </div> -->
</div> </div>

View File

@ -20,7 +20,8 @@
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li><a href="javascript:void(0);" class="js-search" data-close="true"><i class="material-icons">search</i></a></li> <li><a href="javascript:void(0);" class="js-search" data-close="true"><i class="material-icons">search</i></a></li>
<!-- <topbar-languageswitch></topbar-languageswitch> --> <!-- <topbar-languageswitch></topbar-languageswitch> -->
<li class="pull-right"><a href="javascript:void(0);" class="js-right-sidebar" data-close="true"><i class="material-icons">more_vert</i></a></li> <li><a href="javascript:void(0);" class="js-right-sidebar" data-close="true"><i class="material-icons">more_vert</i></a></li>
<li><a (click)="logout()"><i class="material-icons">input</i></a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
import { Component, Injector, ViewEncapsulation } from '@angular/core'; import { Component, Injector, ViewEncapsulation } from '@angular/core';
import { AppComponentBase } from '@shared/app-component-base'; import { AppComponentBase } from '@shared/app-component-base';
import { AppAuthService } from '@shared/auth/app-auth.service';
@Component({ @Component({
templateUrl: './topbar.component.html', templateUrl: './topbar.component.html',
@ -11,8 +12,13 @@ export class TopBarComponent extends AppComponentBase {
public logoUrl: string = '/assets/images/logo.png'; public logoUrl: string = '/assets/images/logo.png';
constructor( constructor(
injector: Injector injector: Injector,
private _authService: AppAuthService
) { ) {
super(injector); super(injector);
} }
logout(): void {
this._authService.logout();
}
} }

View File

@ -0,0 +1,3 @@
h2 {
color: #771111;
}

View File

@ -0,0 +1,58 @@
<div id="profile-area">
<div class="card">
<div #cardBody class="body">
<div>
<h2 class="text-center font-weight-normal">MÓJ PROFIL</h2>
</div>
<form #profileForm="ngForm" method="post" novalidate (ngSubmit)="saveProfile()">
<div class="form-group form-float">
<div class="form-line focused">
<input materialInput class="form-control" type="text" [(ngModel)]="profile.name" name="Name" required maxlength="32" />
<label class="form-label">{{l('Imię')}}</label>
</div>
</div>
<div class="form-group form-float">
<div class="form-line focused">
<input materialInput class="form-control" type="text" [(ngModel)]="profile.surname" name="Surname" required maxlength="32" />
<label class="form-label">{{l('Nazwisko')}}</label>
</div>
</div>
<div class="form-group form-float">
<div class="form-line focused">
<input materialInput class="form-control" type="email" [(ngModel)]="profile.emailAddress" name="EmailAddress" required maxlength="255" pattern="^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$" />
<label class="form-label">{{l('Adres e-mail')}}</label>
</div>
</div>
<div class="form-group form-float">
<div class="form-line focused">
<input materialInput class="form-control" type="text" autocomplete="off" [(ngModel)]="profile.userName" name="UserName" required maxlength="32" />
<label class="form-label">{{l('Login')}}</label>
</div>
</div>
<div *ngIf="profile.participantClass !== 0" class="form-group form-float">
<div class="form-line">
<select class="form-control"
[(ngModel)]="profile.participantClass"
name="ParticipantClass"
required>
<option *ngFor="let possibleClass of possibleClasses" [value]="possibleClass.value">
{{possibleClass.viewValue}}
</option>
</select>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-success" [disabled]="!profileForm.form.valid">{{l("Zapisz profil")}}</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,70 @@
import { Component, Injector, ElementRef, OnInit, OnDestroy, AfterViewInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { UserProfileServiceProxy, UserProfileDto } from '@shared/service-proxies/service-proxies'
import { AppComponentBase } from '@shared/app-component-base';
import { accountModuleAnimation } from '@shared/animations/routerTransition';
import { finalize } from 'rxjs/operators';
import { Subscription } from 'rxjs/Rx';
@Component({
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css'],
animations: [accountModuleAnimation()]
})
export class ProfileComponent extends AppComponentBase implements OnInit, OnDestroy, AfterViewInit {
@ViewChild('cardBody') cardBody: ElementRef;
public profileAreaId: string = 'profile-area';
public profile: UserProfileDto = new UserProfileDto();
private profileSubscription: Subscription;
public possibleClasses = [
{value: 1, viewValue: 'Klasa 1'},
{value: 2, viewValue: 'Klasa 2'},
{value: 3, viewValue: 'Klasa 3'},
{value: 4, viewValue: 'Klasa 4'},
{value: 5, viewValue: 'Klasa 5'},
{value: 6, viewValue: 'Klasa 6'},
{value: 7, viewValue: 'Klasa 7'},
{value: 8, viewValue: 'Klasa 8'}
];
constructor(
injector: Injector,
private userProfileService: UserProfileServiceProxy,
private router: Router,
) {
super(injector);
}
public ngAfterViewInit(): void {
}
public ngOnInit(): void {
this.setBusy(this.profileAreaId);
this.profileSubscription = this.userProfileService.getProfile()
.pipe(finalize(() => { this.clearBusy(this.profileAreaId); }))
.subscribe((result: UserProfileDto) => {
this.profile = result;
});
}
public ngOnDestroy(): void {
if (this.profileSubscription) {
this.profileSubscription.unsubscribe();
}
}
public saveProfile(): void {
this.profile.participantClass = +this.profile.participantClass;
this.userProfileService.updateProfile(this.profile)
.pipe(finalize(() => { this.router.navigate(['app/home']); }))
.subscribe(() => {
this.notify.success(this.l('Zapisano profil'));
});
}
}

View File

@ -2065,6 +2065,122 @@ export class UserServiceProxy {
} }
} }
@Injectable()
export class UserProfileServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : "";
}
/**
* @return Success
*/
getProfile(): Observable<UserProfileDto> {
let url_ = this.baseUrl + "/api/services/app/UserProfile/GetProfile";
url_ = url_.replace(/[?&]$/, "");
let options_ : any = {
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json"
})
};
return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processGetProfile(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processGetProfile(<any>response_);
} catch (e) {
return <Observable<UserProfileDto>><any>_observableThrow(e);
}
} else
return <Observable<UserProfileDto>><any>_observableThrow(response_);
}));
}
protected processGetProfile(response: HttpResponseBase): Observable<UserProfileDto> {
const status = response.status;
const responseBlob =
response instanceof HttpResponse ? response.body :
(<any>response).error instanceof Blob ? (<any>response).error : undefined;
let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }};
if (status === 200) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 ? UserProfileDto.fromJS(resultData200) : new UserProfileDto();
return _observableOf(result200);
}));
} else if (status !== 200 && status !== 204) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}));
}
return _observableOf<UserProfileDto>(<any>null);
}
/**
* @userProfile (optional)
* @return Success
*/
updateProfile(userProfile: UserProfileDto | null | undefined): Observable<void> {
let url_ = this.baseUrl + "/api/services/app/UserProfile/UpdateProfile";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(userProfile);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
})
};
return this.http.request("put", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processUpdateProfile(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processUpdateProfile(<any>response_);
} catch (e) {
return <Observable<void>><any>_observableThrow(e);
}
} else
return <Observable<void>><any>_observableThrow(response_);
}));
}
protected processUpdateProfile(response: HttpResponseBase): Observable<void> {
const status = response.status;
const responseBlob =
response instanceof HttpResponse ? response.body :
(<any>response).error instanceof Blob ? (<any>response).error : undefined;
let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }};
if (status === 200) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return _observableOf<void>(<any>null);
}));
} else if (status !== 200 && status !== 204) {
return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
}));
}
return _observableOf<void>(<any>null);
}
}
export class IsTenantAvailableInput implements IIsTenantAvailableInput { export class IsTenantAvailableInput implements IIsTenantAvailableInput {
tenancyName: string; tenancyName: string;
@ -4389,6 +4505,69 @@ export interface IPagedResultDtoOfUserDto {
items: UserDto[] | undefined; items: UserDto[] | undefined;
} }
export class UserProfileDto implements IUserProfileDto {
name: string;
surname: string;
userName: string;
emailAddress: string;
participantClass: number;
id: number | undefined;
constructor(data?: IUserProfileDto) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}
init(data?: any) {
if (data) {
this.name = data["name"];
this.surname = data["surname"];
this.userName = data["userName"];
this.emailAddress = data["emailAddress"];
this.participantClass = data["participantClass"];
this.id = data["id"];
}
}
static fromJS(data: any): UserProfileDto {
data = typeof data === 'object' ? data : {};
let result = new UserProfileDto();
result.init(data);
return result;
}
toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["name"] = this.name;
data["surname"] = this.surname;
data["userName"] = this.userName;
data["emailAddress"] = this.emailAddress;
data["participantClass"] = this.participantClass;
data["id"] = this.id;
return data;
}
clone(): UserProfileDto {
const json = this.toJSON();
let result = new UserProfileDto();
result.init(json);
return result;
}
}
export interface IUserProfileDto {
name: string;
surname: string;
userName: string;
emailAddress: string;
participantClass: number;
id: number | undefined;
}
export enum IsTenantAvailableOutputState { export enum IsTenantAvailableOutputState {
_1 = 1, _1 = 1,
_2 = 2, _2 = 2,

View File

@ -16,6 +16,7 @@ import * as ApiServiceProxies from './service-proxies';
ApiServiceProxies.CategoryServiceProxy, ApiServiceProxies.CategoryServiceProxy,
ApiServiceProxies.CompetitionCategoryServiceProxy, ApiServiceProxies.CompetitionCategoryServiceProxy,
ApiServiceProxies.CompetitionServiceProxy, ApiServiceProxies.CompetitionServiceProxy,
ApiServiceProxies.UserProfileServiceProxy,
{ provide: HTTP_INTERCEPTORS, useClass: AbpHttpInterceptor, multi: true } { provide: HTTP_INTERCEPTORS, useClass: AbpHttpInterceptor, multi: true }
] ]
}) })

View File

@ -0,0 +1,29 @@
using Abp.Application.Services.Dto;
using Abp.Authorization.Users;
using System.ComponentModel.DataAnnotations;
namespace SystemKonkursow.UserProfile.Dto
{
public class UserProfileDto : EntityDto<long>
{
[Required]
[StringLength(AbpUserBase.MaxNameLength)]
public string Name { get; set; }
[Required]
[StringLength(AbpUserBase.MaxSurnameLength)]
public string Surname { get; set; }
[Required]
[StringLength(AbpUserBase.MaxUserNameLength)]
public string UserName { get; set; }
[Required]
[EmailAddress]
[StringLength(AbpUserBase.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
[Required]
public int ParticipantClass { get; set; }
}
}

View File

@ -0,0 +1,25 @@
using AutoMapper;
using SystemKonkursow.Authorization.Users;
namespace SystemKonkursow.UserProfile.Dto
{
public class UserProfileMapProfile : Profile
{
public UserProfileMapProfile()
{
CreateMap<User, UserProfileDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.Surname, opt => opt.MapFrom(src => src.Surname))
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.UserName))
.ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress));
CreateMap<UserProfileDto, User>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.Surname, opt => opt.MapFrom(src => src.Surname))
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.UserName))
.ForMember(dest => dest.EmailAddress, opt => opt.MapFrom(src => src.EmailAddress));
}
}
}

View File

@ -0,0 +1,68 @@
using Abp.Authorization;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using SystemKonkursow.Authorization.Users;
using SystemKonkursow.UserProfile.Dto;
namespace SystemKonkursow.UserProfile
{
public class UserProfileAppService : SystemKonkursowAppServiceBase
{
private readonly UserManager _userManager;
private readonly IRepository<Domain.Participant, int> _participantRepository;
public UserProfileAppService(UserManager userManager,
IRepository<Domain.Participant, int> participantRepository)
{
_userManager = userManager;
_participantRepository = participantRepository;
}
[AbpAuthorize]
public async Task<UserProfileDto> GetProfile()
{
var user = await GetCurrentUserAsync();
var mappedProfile = ObjectMapper.Map<UserProfileDto>(user);
var isParticipant = await _userManager.IsParticipantUserAsync(user);
if (isParticipant)
{
var participant = await _participantRepository.GetAll()
.FirstOrDefaultAsync(t => t.UserId == user.Id);
mappedProfile.ParticipantClass = participant.ParticipantClass;
}
else
{
mappedProfile.ParticipantClass = 0;
}
return mappedProfile;
}
[AbpAuthorize]
[UnitOfWork]
public async Task UpdateProfile(UserProfileDto userProfile)
{
var user = await _userManager.GetUserByIdAsync(userProfile.Id);
ObjectMapper.Map(userProfile, user);
user.SetNormalizedNames();
CheckErrors(await _userManager.UpdateAsync(user));
if (userProfile.ParticipantClass != 0)
{
var participant = await _participantRepository.GetAll().FirstOrDefaultAsync(t => t.UserId == user.Id);
participant.ParticipantClass = userProfile.ParticipantClass;
await _participantRepository.UpdateAsync(participant);
}
}
}
}