Login, Registration Forms Added.
This commit is contained in:
parent
f8b7acd5c6
commit
2bca44ebe5
@ -1,20 +1,38 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { BehaviorSubject, map } from 'rxjs';
|
||||
import { map, of, ReplaySubject } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { IUser } from '../shared/models/user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AccoutService {
|
||||
export class AccountService {
|
||||
baseUrl = environment.apiUrl;
|
||||
private currentUserSource = new BehaviorSubject<IUser>(null);
|
||||
private currentUserSource = new ReplaySubject<IUser>(1);
|
||||
currentUser$ = this.currentUserSource.asObservable();
|
||||
|
||||
constructor(private http: HttpClient, private router: Router) { }
|
||||
|
||||
loadCurrentUser(token: string){
|
||||
if(token === null){
|
||||
this.currentUserSource.next(null);
|
||||
return of(null);
|
||||
}
|
||||
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.set('Authorization', `Bearer ${token}`);
|
||||
return this.http.get(this.baseUrl + 'account', {headers}).pipe(
|
||||
map((user: IUser) => {
|
||||
if(user){
|
||||
localStorage.setItem('token', user.token);
|
||||
this.currentUserSource.next(user);
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
login(values: any){
|
||||
return this.http.post(this.baseUrl + 'account/login', values).pipe(
|
||||
map((user: IUser) =>{
|
||||
@ -31,6 +49,7 @@ export class AccoutService {
|
||||
map((user:IUser) => {
|
||||
if(user){
|
||||
localStorage.setItem('token', user.token);
|
||||
this.currentUserSource.next(user);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -43,6 +62,6 @@ export class AccoutService {
|
||||
}
|
||||
|
||||
checkEmailExists(email: string){
|
||||
return this.http.get(this.baseUrl + '/account/emailexists?email=' + email);
|
||||
return this.http.get(this.baseUrl + 'account/emailexists?email=' + email);
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,10 @@
|
||||
<div class="d-flex justify-content-center mt-5">
|
||||
<div class="col-3">
|
||||
<form>
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||
<h1 class="h3 mb-3 fw-normal text-center">Login</h1>
|
||||
|
||||
<div class="form-floating mb-2">
|
||||
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
|
||||
<label for="floatingInput">Email address</label>
|
||||
</div>
|
||||
<div class="form-floating mb-2">
|
||||
<input type="password" class="form-control" id="floatingPassword" placeholder="Password">
|
||||
<label for="floatingPassword">Password</label>
|
||||
</div>
|
||||
<button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
|
||||
<app-text-inputs formControlName="email" [label]="'Email Address'"></app-text-inputs>
|
||||
<app-text-inputs formControlName="password" [label]="'Password'" [type]="'password'"></app-text-inputs>
|
||||
<button [disabled]="loginForm.invalid" class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AccountService } from '../account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
@ -8,18 +10,30 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
loginForm: FormGroup;
|
||||
returnUrl: string;
|
||||
|
||||
|
||||
constructor() { }
|
||||
constructor(private accountService: AccountService, private router: Router, private activatedRoute: ActivatedRoute) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.returnUrl = this.activatedRoute.snapshot.queryParams.returnUrl || '/shop';
|
||||
this.createLoginForm();
|
||||
}
|
||||
|
||||
createLoginForm(){
|
||||
this.loginForm = new FormGroup({
|
||||
email: new FormControl('', Validators.required),
|
||||
email: new FormControl('', [Validators.required, Validators.pattern('^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}')]),
|
||||
password: new FormControl('', Validators.required)
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(){
|
||||
this.accountService.login(this.loginForm.value).subscribe({
|
||||
next: () => { this.router.navigateByUrl(this.returnUrl); },
|
||||
error: (e: any) => { console.log(e) },
|
||||
complete: () => { console.log('complete')}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +1,17 @@
|
||||
<p>register works!</p>
|
||||
<div class="d-flex justify-content-center mt-5">
|
||||
<div class="col-3">
|
||||
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
|
||||
<h1 class="h3 mb-3 fw-normal text-center">Register</h1>
|
||||
<app-text-inputs formControlName="displayName" [label]="'Display Name'"></app-text-inputs>
|
||||
<app-text-inputs formControlName="email" [label]="'Email Address'"></app-text-inputs>
|
||||
<app-text-inputs formControlName="password" [label]="'Password'" [type]="'password'"></app-text-inputs>
|
||||
<ul class="text-warning list-unstyled" *ngIf="errors">
|
||||
<li *ngFor="let error of errors">
|
||||
{{error}}
|
||||
</li>
|
||||
</ul>
|
||||
<button [disabled]="registerForm.invalid" class="w-100 btn btn-lg btn-primary" type="submit">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { AsyncValidatorFn, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { map, of, switchMap, timer } from 'rxjs';
|
||||
import { AccountService } from '../account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
@ -6,10 +10,52 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./register.component.scss']
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
registerForm: FormGroup;
|
||||
errors: string[];
|
||||
|
||||
constructor() { }
|
||||
constructor(private fb: FormBuilder, private accountService: AccountService, private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.createRegisterForm();
|
||||
}
|
||||
|
||||
createRegisterForm(){
|
||||
this.registerForm = this.fb.group({
|
||||
displayName: [null, [Validators.required]],
|
||||
email: [null,
|
||||
[Validators.required, Validators.pattern('^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}')],
|
||||
[this.validateEmailNotTaken()]
|
||||
],
|
||||
password: [null, [Validators.required],]
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(){
|
||||
this.accountService.register(this.registerForm.value).subscribe({
|
||||
next: (response) => { this.router.navigateByUrl('/shop')},
|
||||
error: (e: any) => {
|
||||
console.log(e);
|
||||
this.errors = e.errors;
|
||||
},
|
||||
complete: () => { console.log('completed') }
|
||||
});
|
||||
}
|
||||
|
||||
validateEmailNotTaken(): AsyncValidatorFn {
|
||||
return control => {
|
||||
return timer(500).pipe(
|
||||
switchMap(() => {
|
||||
if(!control.value){
|
||||
return of(null);
|
||||
}
|
||||
return this.accountService.checkEmailExists(control.value).pipe(
|
||||
map(res => {
|
||||
return res ? {emailExists: true} : null;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from './core/guards/auth.guard';
|
||||
import { NotFoundComponent } from './core/not-found/not-found.component';
|
||||
import { ServerErrorComponent } from './core/server-error/server-error.component';
|
||||
import { TestErrorComponent } from './core/test-error/test-error.component';
|
||||
@ -12,7 +13,7 @@ const routes: Routes = [
|
||||
{path: 'not-found', component: NotFoundComponent, data: {breadcrumb: 'Not Found'}},
|
||||
{path: 'shop', loadChildren: ()=> import('./shop/shop.module').then(mod => mod.ShopModule), data: {breadcrumb: 'Shop'}},
|
||||
{path: 'basket', loadChildren: ()=> import('./basket/basket.module').then(mod => mod.BasketModule), data: {breadcrumb: 'Shopping Cart'}},
|
||||
{path: 'checkout', loadChildren: ()=> import('./checkout/checkout.module').then(mod => mod.CheckoutModule), data: {breadcrumb: 'Checkout'}},
|
||||
{path: 'checkout', canActivate: [AuthGuard], loadChildren: ()=> import('./checkout/checkout.module').then(mod => mod.CheckoutModule), data: {breadcrumb: 'Checkout'}},
|
||||
{path: 'account', loadChildren: ()=> import('./account/account.module').then(mod => mod.AccountModule), data: {breadcrumb: {skip: true}}},
|
||||
{path: '**', redirectTo: 'not-found', pathMatch: 'full'}
|
||||
];
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { BasketService } from './basket/basket.service';
|
||||
import { AccountService } from './account/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -9,9 +10,23 @@ import { BasketService } from './basket/basket.service';
|
||||
export class AppComponent implements OnInit {
|
||||
title = 'SkiNet';
|
||||
|
||||
constructor(private basketService: BasketService) {}
|
||||
constructor(private basketService: BasketService, private accountService: AccountService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadBasket();
|
||||
this.loadCurrentUser();
|
||||
}
|
||||
|
||||
loadCurrentUser(){
|
||||
const token = localStorage.getItem('token');
|
||||
this.accountService.loadCurrentUser(token).subscribe({
|
||||
next: () => { console.log('loader user') },
|
||||
error: (e: any) => { console.log(e) },
|
||||
complete: () => { console.log('completed') }
|
||||
});
|
||||
}
|
||||
|
||||
loadBasket(){
|
||||
const basketId = localStorage.getItem('basket_id');
|
||||
if(basketId){
|
||||
this.basketService.getBasket(basketId).subscribe({
|
||||
|
@ -8,6 +8,7 @@ import { ServerErrorComponent } from './server-error/server-error.component';
|
||||
import { ToastrModule } from 'ngx-toastr';
|
||||
import { SectionHeaderComponent } from './section-header/section-header.component';
|
||||
import { BreadcrumbModule } from 'xng-breadcrumb';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
|
||||
|
||||
@ -17,6 +18,7 @@ import { BreadcrumbModule } from 'xng-breadcrumb';
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
BreadcrumbModule,
|
||||
SharedModule,
|
||||
ToastrModule.forRoot({
|
||||
positionClass: 'toast-bottom-right',
|
||||
preventDuplicates: true
|
||||
|
24
client/src/app/core/guards/auth.guard.ts
Normal file
24
client/src/app/core/guards/auth.guard.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { AccountService } from 'src/app/account/account.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private accountService: AccountService, private router: Router) {}
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot): Observable<boolean> {
|
||||
return this.accountService.currentUser$.pipe(
|
||||
map(auth => {
|
||||
if(auth){
|
||||
return true;
|
||||
}
|
||||
this.router.navigate(['account/login'], {queryParams: {returnUrl: state.url}});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -8,7 +8,10 @@ export class LoadingInterceptor implements HttpInterceptor {
|
||||
constructor(private busyService: BusyService) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
this.busyService.busy();
|
||||
if(!req.url.includes('emailexists')){
|
||||
this.busyService.busy();
|
||||
}
|
||||
|
||||
return next.handle(req).pipe(
|
||||
delay(1000),
|
||||
finalize(()=> {
|
||||
|
@ -10,8 +10,29 @@
|
||||
<i class="fa fa-shopping-cart fa-2x me-3 text-dark"></i>
|
||||
<div *ngIf="(basket$ | async) as basket" class="cart-no">{{basket.items.length}}</div>
|
||||
</a>
|
||||
<a routerLink="/account/login" class="btn btn-outline-secondary me-3" href="#">Login</a>
|
||||
<a routerLink="/account/register" class="btn btn-outline-secondary me-3" href="#">Sign up</a>
|
||||
<ng-container *ngIf="(currentUser$ | async) === null">
|
||||
<a routerLink="/account/login" class="btn btn-outline-secondary ms-3 me-3" href="#">Login</a>
|
||||
<a routerLink="/account/register" class="btn btn-outline-secondary me-3" href="#">Sign up</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="(currentUser$ | async) as user">
|
||||
<div class="dropdown ms-3 me-5" dropdown>
|
||||
<a class="dropdown-toggle" style="cursor: pointer" dropdownToggle>
|
||||
<strong>Welcome {{(user.displayName)}}</strong>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" style="cursor: pointer" *dropdownMenu>
|
||||
<a routerLink="/basket" class="dropdown-item d-flex align-items-center py-2">
|
||||
<i class="fa fa-shopping-cart me-3"></i> View Basket
|
||||
</a>
|
||||
<a routerLink="/orders" class="dropdown-item d-flex align-items-center py-2">
|
||||
<i class="fa fa-history me-3"></i> View Orders
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a (click)="logout()" class="dropdown-item d-flex align-items-center py-2">
|
||||
<i class="fa fa-sign-out me-3"></i> Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AccountService } from 'src/app/account/account.service';
|
||||
import { BasketService } from 'src/app/basket/basket.service';
|
||||
import { IBasket } from 'src/app/shared/models/baskset';
|
||||
import { IUser } from 'src/app/shared/models/user';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav-bar',
|
||||
@ -9,12 +11,18 @@ import { IBasket } from 'src/app/shared/models/baskset';
|
||||
styleUrls: ['./nav-bar.component.scss']
|
||||
})
|
||||
export class NavBarComponent implements OnInit {
|
||||
basket$ : Observable<IBasket>
|
||||
basket$ : Observable<IBasket>;
|
||||
currentUser$: Observable<IUser>;
|
||||
|
||||
constructor(private basketService: BasketService) { }
|
||||
constructor(private basketService: BasketService, private accountService: AccountService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.basket$ = this.basketService.basket$;
|
||||
this.currentUser$ = this.accountService.currentUser$;
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.accountService.logout();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
<div class="form-floating mb-2">
|
||||
<input
|
||||
[ngClass]="(controlDir && controlDir.control && controlDir.control.touched) ? !controlDir.control.valid ? 'is-invalid' : 'is-valid' : null"
|
||||
[type]="type"
|
||||
(input)="onChange($event.target.value)"
|
||||
(blur)="onTouched()"
|
||||
class="form-control"
|
||||
id="{{label}}"
|
||||
#input
|
||||
placeholder="{{label}}"
|
||||
>
|
||||
<div *ngIf="controlDir && controlDir.control && controlDir.control.status === 'PENDING'" class="fa fa-spinner fa-spin loader"></div>
|
||||
<label for="{{label}}">{{label}}</label>
|
||||
<div class="text-warning" *ngIf="(controlDir && controlDir.control && !controlDir.control.valid && controlDir.control.touched)">
|
||||
<span *ngIf="controlDir.control.errors?.required">{{label}} is required</span>
|
||||
<span *ngIf="controlDir.control.errors?.pattern">Email is invalid</span>
|
||||
</div>
|
||||
<div class="text-warning d-block" *ngIf="(controlDir && controlDir.control && !controlDir.control.valid && controlDir.control.dirty)">
|
||||
<span *ngIf="controlDir.control.errors?.emailExists">Email Address is in use.</span>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
.loader {
|
||||
position: absolute;
|
||||
width: auto;
|
||||
top: 20px;
|
||||
right: 10px;
|
||||
margin-top: 0;
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { Component, ElementRef, Input, OnInit, Self, ViewChild } from '@angular/core';
|
||||
import { ControlValueAccessor, NgControl } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-text-inputs',
|
||||
templateUrl: './text-inputs.component.html',
|
||||
styleUrls: ['./text-inputs.component.scss']
|
||||
})
|
||||
export class TextInputsComponent implements OnInit, ControlValueAccessor {
|
||||
@ViewChild('input', {static: true}) input: ElementRef;
|
||||
@Input() type = 'text';
|
||||
@Input() label: string;
|
||||
|
||||
constructor(@Self() public controlDir: NgControl) {
|
||||
this.controlDir.valueAccessor = this;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const control = this.controlDir.control;
|
||||
const validators = control.validator ? [control.validator] : [];
|
||||
const asyncValidators = control.asyncValidator ? [control.asyncValidator] : [];
|
||||
|
||||
control.setValidators(validators);
|
||||
control.setAsyncValidators(asyncValidators);
|
||||
control.updateValueAndValidity();
|
||||
}
|
||||
|
||||
onChange(evt) {}
|
||||
onTouched(evt?) {}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.input.nativeElement.value = obj || '';
|
||||
}
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
}
|
@ -6,19 +6,22 @@ import { CarouselModule } from 'ngx-bootstrap/carousel';
|
||||
import { PagingHeaderComponent } from './components/paging-header/paging-header.component';
|
||||
import { PagerComponent } from './components/pager/pager.component';
|
||||
import { OrderTotalsComponent } from './components/order-totals/order-totals.component';
|
||||
|
||||
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
|
||||
import { TextInputsComponent } from './components/text-inputs/text-inputs.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
PagingHeaderComponent,
|
||||
PagerComponent,
|
||||
OrderTotalsComponent
|
||||
OrderTotalsComponent,
|
||||
TextInputsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
PaginationModule.forRoot(),
|
||||
CarouselModule.forRoot(),
|
||||
BsDropdownModule.forRoot(),
|
||||
ReactiveFormsModule
|
||||
],
|
||||
exports: [
|
||||
@ -27,7 +30,9 @@ import { OrderTotalsComponent } from './components/order-totals/order-totals.com
|
||||
PagerComponent,
|
||||
CarouselModule,
|
||||
OrderTotalsComponent,
|
||||
ReactiveFormsModule
|
||||
ReactiveFormsModule,
|
||||
BsDropdownModule,
|
||||
TextInputsComponent
|
||||
]
|
||||
})
|
||||
export class SharedModule { }
|
||||
|
Loading…
Reference in New Issue
Block a user