From 304336a697d90c393b3566c5e0afb30609417375 Mon Sep 17 00:00:00 2001 From: erdar2 Date: Sat, 11 Dec 2021 12:20:44 +0100 Subject: [PATCH] function: authentication with firebase --- src/app/app-routing.module.ts | 6 +- src/app/app.component.html | 32 ++----- src/app/app.component.less | 50 ++--------- src/app/app.component.ts | 76 +--------------- src/app/app.module.ts | 18 ++-- .../phone-book/phone-book.component.html | 24 +++++ .../phone-book/phone-book.component.less | 35 ++++++++ .../phone-book/phone-book.component.spec.ts | 25 ++++++ .../phone-book/phone-book.component.ts | 88 +++++++++++++++++++ .../record-dialog.component.html | 0 .../record-dialog.component.less | 0 .../record-dialog.component.spec.ts | 0 .../record-dialog/record-dialog.component.ts | 0 .../record/record.component.html | 0 .../record/record.component.less | 0 .../record/record.component.spec.ts | 0 .../record/record.component.ts | 0 src/app/{ => components}/record/record.ts | 0 src/app/components/sign-in/formData.ts | 4 + .../components/sign-in/sign-in.component.html | 15 ++++ .../components/sign-in/sign-in.component.less | 9 ++ .../sign-in/sign-in.component.spec.ts | 25 ++++++ .../components/sign-in/sign-in.component.ts | 30 +++++++ src/app/shared/guard/auth.guard.spec.ts | 16 ++++ src/app/shared/guard/auth.guard.ts | 26 ++++++ src/app/shared/services/auth.service.spec.ts | 16 ++++ src/app/shared/services/auth.service.ts | 69 +++++++++++++++ src/app/shared/services/user.ts | 7 ++ 28 files changed, 426 insertions(+), 145 deletions(-) create mode 100644 src/app/components/phone-book/phone-book.component.html create mode 100644 src/app/components/phone-book/phone-book.component.less create mode 100644 src/app/components/phone-book/phone-book.component.spec.ts create mode 100644 src/app/components/phone-book/phone-book.component.ts rename src/app/{ => components}/record-dialog/record-dialog.component.html (100%) rename src/app/{ => components}/record-dialog/record-dialog.component.less (100%) rename src/app/{ => components}/record-dialog/record-dialog.component.spec.ts (100%) rename src/app/{ => components}/record-dialog/record-dialog.component.ts (100%) rename src/app/{ => components}/record/record.component.html (100%) rename src/app/{ => components}/record/record.component.less (100%) rename src/app/{ => components}/record/record.component.spec.ts (100%) rename src/app/{ => components}/record/record.component.ts (100%) rename src/app/{ => components}/record/record.ts (100%) create mode 100644 src/app/components/sign-in/formData.ts create mode 100644 src/app/components/sign-in/sign-in.component.html create mode 100644 src/app/components/sign-in/sign-in.component.less create mode 100644 src/app/components/sign-in/sign-in.component.spec.ts create mode 100644 src/app/components/sign-in/sign-in.component.ts create mode 100644 src/app/shared/guard/auth.guard.spec.ts create mode 100644 src/app/shared/guard/auth.guard.ts create mode 100644 src/app/shared/services/auth.service.spec.ts create mode 100644 src/app/shared/services/auth.service.ts create mode 100644 src/app/shared/services/user.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 3ba5928..ab134f4 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,8 +1,12 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { PhoneBookComponent } from './components/phone-book/phone-book.component'; +import { SignInComponent } from './components/sign-in/sign-in.component'; +import { AuthGuard } from './shared/guard/auth.guard'; const routes: Routes = [ - + { path: '', component: PhoneBookComponent, canActivate: [AuthGuard] }, + { path: 'sign-in', component: SignInComponent }, ]; @NgModule({ diff --git a/src/app/app.component.html b/src/app/app.component.html index a7cd137..55d0148 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,30 +1,10 @@ Phone Book + +
- - - search - Search - - - -
-
- - -

- No results -

-
-
-
-
- + + \ No newline at end of file diff --git a/src/app/app.component.less b/src/app/app.component.less index 0483b46..50eea63 100644 --- a/src/app/app.component.less +++ b/src/app/app.component.less @@ -1,43 +1,11 @@ mat-toolbar { - margin-bottom: 20px; - } - - mat-toolbar > span { - margin-left: 10px; - } - - .content-wrapper { - max-width: 1400px; - margin: auto; - } - - .container-wrapper { - display: flex; - justify-content: space-around; - } - - .container { - width: 100%; - margin: 0 25px 25px 0; - } - - .list { - border: solid 1px #ccc; - min-height: 60px; - border-radius: 4px; - } - - .new-record { - margin-bottom: 30px; - } - - .empty-label { - font-size: 2em; - padding-top: 10px; - text-align: center; - opacity: 0.2; - } + margin-bottom: 20px; +} - .content-wrapper > *{ - margin: 4px 4px 0px; - } \ No newline at end of file +mat-toolbar > span { + margin-left: 10px; +} + +.spacer { + flex: 1 1 auto; +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 19090c3..e3e677a 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,12 +1,5 @@ import { Component } from '@angular/core'; -import { Record } from './record/record'; -import { MatDialog } from '@angular/material/dialog'; -import { - RecordDialogComponent, - RecordDialogResult, -} from './record-dialog/record-dialog.component'; -import { AngularFirestore, Query } from '@angular/fire/compat/firestore'; -import { Observable } from 'rxjs'; +import { AuthService } from './shared/services/auth.service'; @Component({ selector: 'app-root', @@ -15,72 +8,11 @@ import { Observable } from 'rxjs'; }) export class AppComponent { title = 'PhoneBook'; - phoneBook = this.store - .collection('phoneBook') - .valueChanges({ idField: 'id' }) as Observable; - searchValue = ''; - phoneBookLocal : Record[] = []; - phoneBookObserver = { - next: (x: Record[]) => { - this.phoneBookLocal = x; - }, - error: (err: String) => console.error('Observer got an error: ' + err), - complete: () => console.log('Observer got a complete notification'), - }; + constructor(public authService : AuthService){ - constructor(private dialog: MatDialog, private store: AngularFirestore) { - this.phoneBook.subscribe(this.phoneBookObserver); } - newRecord(): void { - const dialogRef = this.dialog.open(RecordDialogComponent, { - width: '270px', - data: { - record: {}, - }, - }); - dialogRef - .afterClosed() - .subscribe((result: RecordDialogResult | undefined) => { - if (!result) { - return; - } - this.store.collection('phoneBook').add(result.record); - }); - } - - editRecord(record: Record): void { - const dialogRef = this.dialog.open(RecordDialogComponent, { - width: '270px', - data: { - record: { - name: record.name, - phoneNumber: record.phoneNumber, - }, - }, - }); - dialogRef - .afterClosed() - .subscribe((result: RecordDialogResult | undefined) => { - if (!result) { - return; - } - this.store.collection('phoneBook').doc(record.id).update(result.record); - }); - } - - deleteRecord(record: Record): void { - this.store.collection('phoneBook').doc(record.id).delete(); - } - - search(event: Event): void { - this.searchValue = (event.target as HTMLInputElement).value; - } - - filteredPhoneBook(): Record[]{ - if(this.searchValue == "") - return this.phoneBookLocal; - - return this.phoneBookLocal.filter(p => p.name.includes(this.searchValue) || p.phoneNumber.includes(this.searchValue)); + logout(): void { + this.authService.logout(); } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 500675e..90c9c2e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,24 +6,31 @@ import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { RecordComponent } from './record/record.component'; +import { RecordComponent } from './components/record/record.component'; import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; -import { RecordDialogComponent } from './record-dialog/record-dialog.component'; +import { RecordDialogComponent } from './components/record-dialog/record-dialog.component'; import { MatInputModule } from '@angular/material/input'; import { FormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { MatGridListModule } from '@angular/material/grid-list'; + import { environment } from 'src/environments/environment'; import { AngularFireModule } from '@angular/fire/compat'; import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; +import { AngularFireAuthModule } from "@angular/fire/compat/auth"; +import { SignInComponent } from './components/sign-in/sign-in.component'; +import { PhoneBookComponent } from './components/phone-book/phone-book.component'; +import { AuthService } from './shared/services/auth.service'; @NgModule({ declarations: [ AppComponent, RecordComponent, - RecordDialogComponent + RecordDialogComponent, + SignInComponent, + PhoneBookComponent ], imports: [ BrowserModule, @@ -38,9 +45,10 @@ import { AngularFirestoreModule } from '@angular/fire/compat/firestore'; MatIconModule, MatGridListModule, AngularFireModule.initializeApp(environment.firebase), - AngularFirestoreModule + AngularFirestoreModule, + AngularFireAuthModule ], - providers: [], + providers: [AuthService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/components/phone-book/phone-book.component.html b/src/app/components/phone-book/phone-book.component.html new file mode 100644 index 0000000..367292e --- /dev/null +++ b/src/app/components/phone-book/phone-book.component.html @@ -0,0 +1,24 @@ + + + search + Search + + + +
+
+ + +

+ No results +

+
+
+
diff --git a/src/app/components/phone-book/phone-book.component.less b/src/app/components/phone-book/phone-book.component.less new file mode 100644 index 0000000..48e8cec --- /dev/null +++ b/src/app/components/phone-book/phone-book.component.less @@ -0,0 +1,35 @@ +.content-wrapper { + max-width: 1400px; + margin: auto; +} + +.container-wrapper { + display: flex; + justify-content: space-around; +} + +.container { + width: 100%; + margin: 0 25px 25px 0; +} + +.list { + border: solid 1px #ccc; + min-height: 60px; + border-radius: 4px; +} + +.new-record { + margin-bottom: 30px; +} + +.empty-label { + font-size: 2em; + padding-top: 10px; + text-align: center; + opacity: 0.2; +} + +.content-wrapper > * { + margin: 4px 4px 0px; +} diff --git a/src/app/components/phone-book/phone-book.component.spec.ts b/src/app/components/phone-book/phone-book.component.spec.ts new file mode 100644 index 0000000..1f7b22a --- /dev/null +++ b/src/app/components/phone-book/phone-book.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PhoneBookComponent } from './phone-book.component'; + +describe('PhoneBookComponent', () => { + let component: PhoneBookComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PhoneBookComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PhoneBookComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/phone-book/phone-book.component.ts b/src/app/components/phone-book/phone-book.component.ts new file mode 100644 index 0000000..33d298d --- /dev/null +++ b/src/app/components/phone-book/phone-book.component.ts @@ -0,0 +1,88 @@ +import { Component } from '@angular/core'; +import { Record } from '../record/record'; +import { MatDialog } from '@angular/material/dialog'; +import { + RecordDialogComponent, + RecordDialogResult, +} from '../record-dialog/record-dialog.component'; +import { AngularFirestore } from '@angular/fire/compat/firestore'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-phone-book', + templateUrl: './phone-book.component.html', + styleUrls: ['./phone-book.component.less'], +}) +export class PhoneBookComponent { + phoneBook = this.store + .collection('phoneBook') + .valueChanges({ idField: 'id' }) as Observable; + searchValue = ''; + phoneBookLocal: Record[] = []; + phoneBookObserver = { + next: (x: Record[]) => { + this.phoneBookLocal = x; + }, + error: (err: String) => console.error('Observer got an error: ' + err), + complete: () => console.log('Observer got a complete notification'), + }; + + constructor(private dialog: MatDialog, private store: AngularFirestore) { + this.phoneBook.subscribe(this.phoneBookObserver); + } + + newRecord(): void { + const dialogRef = this.dialog.open(RecordDialogComponent, { + width: '270px', + data: { + record: {}, + }, + }); + dialogRef + .afterClosed() + .subscribe((result: RecordDialogResult | undefined) => { + if (!result) { + return; + } + this.store.collection('phoneBook').add(result.record); + }); + } + + editRecord(record: Record): void { + const dialogRef = this.dialog.open(RecordDialogComponent, { + width: '270px', + data: { + record: { + name: record.name, + phoneNumber: record.phoneNumber, + }, + }, + }); + dialogRef + .afterClosed() + .subscribe((result: RecordDialogResult | undefined) => { + if (!result) { + return; + } + this.store.collection('phoneBook').doc(record.id).update(result.record); + }); + } + + deleteRecord(record: Record): void { + this.store.collection('phoneBook').doc(record.id).delete(); + } + + search(event: Event): void { + this.searchValue = (event.target as HTMLInputElement).value; + } + + filteredPhoneBook(): Record[] { + if (this.searchValue == '') return this.phoneBookLocal; + + return this.phoneBookLocal.filter( + (p) => + p.name.includes(this.searchValue) || + p.phoneNumber.includes(this.searchValue) + ); + } +} diff --git a/src/app/record-dialog/record-dialog.component.html b/src/app/components/record-dialog/record-dialog.component.html similarity index 100% rename from src/app/record-dialog/record-dialog.component.html rename to src/app/components/record-dialog/record-dialog.component.html diff --git a/src/app/record-dialog/record-dialog.component.less b/src/app/components/record-dialog/record-dialog.component.less similarity index 100% rename from src/app/record-dialog/record-dialog.component.less rename to src/app/components/record-dialog/record-dialog.component.less diff --git a/src/app/record-dialog/record-dialog.component.spec.ts b/src/app/components/record-dialog/record-dialog.component.spec.ts similarity index 100% rename from src/app/record-dialog/record-dialog.component.spec.ts rename to src/app/components/record-dialog/record-dialog.component.spec.ts diff --git a/src/app/record-dialog/record-dialog.component.ts b/src/app/components/record-dialog/record-dialog.component.ts similarity index 100% rename from src/app/record-dialog/record-dialog.component.ts rename to src/app/components/record-dialog/record-dialog.component.ts diff --git a/src/app/record/record.component.html b/src/app/components/record/record.component.html similarity index 100% rename from src/app/record/record.component.html rename to src/app/components/record/record.component.html diff --git a/src/app/record/record.component.less b/src/app/components/record/record.component.less similarity index 100% rename from src/app/record/record.component.less rename to src/app/components/record/record.component.less diff --git a/src/app/record/record.component.spec.ts b/src/app/components/record/record.component.spec.ts similarity index 100% rename from src/app/record/record.component.spec.ts rename to src/app/components/record/record.component.spec.ts diff --git a/src/app/record/record.component.ts b/src/app/components/record/record.component.ts similarity index 100% rename from src/app/record/record.component.ts rename to src/app/components/record/record.component.ts diff --git a/src/app/record/record.ts b/src/app/components/record/record.ts similarity index 100% rename from src/app/record/record.ts rename to src/app/components/record/record.ts diff --git a/src/app/components/sign-in/formData.ts b/src/app/components/sign-in/formData.ts new file mode 100644 index 0000000..a1327b6 --- /dev/null +++ b/src/app/components/sign-in/formData.ts @@ -0,0 +1,4 @@ +export interface formData{ + email: string; + password: string; +} \ No newline at end of file diff --git a/src/app/components/sign-in/sign-in.component.html b/src/app/components/sign-in/sign-in.component.html new file mode 100644 index 0000000..be9b98b --- /dev/null +++ b/src/app/components/sign-in/sign-in.component.html @@ -0,0 +1,15 @@ +
+ + + Email + + + + + Password + + + + + +
diff --git a/src/app/components/sign-in/sign-in.component.less b/src/app/components/sign-in/sign-in.component.less new file mode 100644 index 0000000..b527626 --- /dev/null +++ b/src/app/components/sign-in/sign-in.component.less @@ -0,0 +1,9 @@ +:host { + display: flex; + flex-direction: column; + align-items: center; + } + + mat-card > *{ + display: block; + } \ No newline at end of file diff --git a/src/app/components/sign-in/sign-in.component.spec.ts b/src/app/components/sign-in/sign-in.component.spec.ts new file mode 100644 index 0000000..e0d4c2f --- /dev/null +++ b/src/app/components/sign-in/sign-in.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SignInComponent } from './sign-in.component'; + +describe('SignInComponent', () => { + let component: SignInComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SignInComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SignInComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/sign-in/sign-in.component.ts b/src/app/components/sign-in/sign-in.component.ts new file mode 100644 index 0000000..fa4b60e --- /dev/null +++ b/src/app/components/sign-in/sign-in.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { AuthService } from '../../shared/services/auth.service'; +import { formData } from './formData'; + +@Component({ + selector: 'app-sign-in', + templateUrl: './sign-in.component.html', + styleUrls: ['./sign-in.component.less'] +}) +export class SignInComponent implements OnInit { + + data: formData = { + email: "", + password: "" + }; + + + constructor(private authService: AuthService) {} + + ngOnInit(): void { + } + + login() { + this.authService.login( + this.data.email, + this.data.password + ); + } + +} diff --git a/src/app/shared/guard/auth.guard.spec.ts b/src/app/shared/guard/auth.guard.spec.ts new file mode 100644 index 0000000..68889d2 --- /dev/null +++ b/src/app/shared/guard/auth.guard.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthGuard } from './auth.guard'; + +describe('AuthGuard', () => { + let guard: AuthGuard; + + beforeEach(() => { + TestBed.configureTestingModule({}); + guard = TestBed.inject(AuthGuard); + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); +}); diff --git a/src/app/shared/guard/auth.guard.ts b/src/app/shared/guard/auth.guard.ts new file mode 100644 index 0000000..448e52e --- /dev/null +++ b/src/app/shared/guard/auth.guard.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from '../services/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + + constructor( + public authService: AuthService, + public router: Router + ){ } + + + canActivate( + next: ActivatedRouteSnapshot, + state: RouterStateSnapshot): Observable | Promise | boolean { + if(this.authService.isLoggedIn !== true) { + this.router.navigate(['sign-in']) + } + return true; + } + +} diff --git a/src/app/shared/services/auth.service.spec.ts b/src/app/shared/services/auth.service.spec.ts new file mode 100644 index 0000000..f1251ca --- /dev/null +++ b/src/app/shared/services/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/services/auth.service.ts b/src/app/shared/services/auth.service.ts new file mode 100644 index 0000000..8acb76a --- /dev/null +++ b/src/app/shared/services/auth.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/compat/firestore'; +import { AngularFireAuth } from '@angular/fire/compat/auth'; +import * as firebase from 'firebase/app'; +import { User } from './user'; + +@Injectable() +export class AuthService { + + userData: any; + + constructor( + public afs: AngularFirestore, // Inject Firestore service + public afAuth: AngularFireAuth, // Inject Firebase auth service + public router: Router + ) { + /* Saving user data in localstorage when + logged in and setting up null when logged out */ + this.afAuth.authState.subscribe(user => { + if (user) { + this.userData = user; + localStorage.setItem('user', JSON.stringify(this.userData)); + JSON.parse(localStorage.getItem('user') as string); + } else { + localStorage.setItem('user', null as any); + JSON.parse(localStorage.getItem('user') as string); + } + }) + } + + login(email : string, password : string) { + return this.afAuth.signInWithEmailAndPassword(email, password) + .then((result) => { + this.SetUserData(result.user as User); + localStorage.setItem('user', JSON.stringify(result.user)); + JSON.parse(localStorage.getItem('user') as string); + this.router.navigate(['']); + }).catch((error) => { + window.alert(error.message) + }) + } + + get isLoggedIn(): boolean { + const user = JSON.parse(localStorage.getItem('user') as string); + return user !== null; + } + + SetUserData(user: User) { + const userRef: AngularFirestoreDocument = this.afs.doc(`users/${user.uid}`); + const userData: User = { + uid: user.uid, + email: user.email, + displayName: user.displayName, + photoURL: user.photoURL, + emailVerified: user.emailVerified + } + return userRef.set(userData, { + merge: true + }) + } + + logout() { + return this.afAuth.signOut().then(() => { + localStorage.removeItem('user'); + this.router.navigate(['sign-in']); + }) + } +} \ No newline at end of file diff --git a/src/app/shared/services/user.ts b/src/app/shared/services/user.ts new file mode 100644 index 0000000..9c27966 --- /dev/null +++ b/src/app/shared/services/user.ts @@ -0,0 +1,7 @@ +export interface User { + uid: string; + email: string; + displayName: string; + photoURL: string; + emailVerified: boolean; + } \ No newline at end of file