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 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
+
+
+
+
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