diff --git a/client/package.json b/client/package.json index 43176c6..f07cf12 100644 --- a/client/package.json +++ b/client/package.json @@ -31,14 +31,14 @@ "@qontu/ngx-inline-editor": "^0.2.0-alpha.12", "angular2-jwt": "^0.2.3", "angular2-moment": "^1.8.0", - "angularfire2": "^5.0.0-rc.6", + "angularfire2": "^5.0.0-rc.7", "angularx-social-login": "^1.1.8", "applicationinsights-js": "^1.0.15", "bootstrap": "4.1.0", "cookieconsent": "^3.0.6", "core-js": "^2.5.3", "dropzone": "^5.3.0", - "firebase": "4.12.1", + "firebase": "^4.12.1", "font-awesome": "^4.7.0", "howler": "^2.0.9", "jquery": "^3.3.1", diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 3ffcc29..2f846d4 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -15,3 +15,5 @@ + + diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index fed53b8..7c1fe54 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -16,6 +16,7 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar'; import { AngularFireDatabaseModule } from 'angularfire2/database'; import { AngularFireAuthModule } from 'angularfire2/auth'; import { AngularFireModule } from 'angularfire2'; +import { AngularFirestoreModule } from 'angularfire2/firestore'; import { QuillModule } from 'ngx-quill'; import { SocialLoginModule, AuthServiceConfig } from 'angularx-social-login'; @@ -77,6 +78,8 @@ import { SideOverlayComponent } from './components/side-overlay/side-overlay.com import { UiStateService } from './services/ui-state.service'; import { BoilerplateComponent } from './components/boilerplate/boilerplate.component'; import { BasePageComponent } from './components/base-page/base-page.component'; +import { ChatWidgetComponent } from './components/chat-widget/chat-widget.component'; +import { FirebaseAuthService } from './services/firebase-auth.service'; const cookieConfig: NgcCookieConsentConfig = { cookie: { @@ -143,20 +146,22 @@ export function provideConfig() { HumaniseTimePipe, SideOverlayComponent, BoilerplateComponent, - BasePageComponent + BasePageComponent, + ChatWidgetComponent ], imports: [ BrowserModule, AngularFireModule.initializeApp({ - apiKey: 'AIzaSyAaIm8LTB0ZgJ-g7RXEjtVa1EOQB381QLI', - authDomain: 'podnoms-797e3.firebaseapp.com', - databaseURL: 'https://podnoms-797e3.firebaseio.com', - projectId: 'podnoms-797e3', - storageBucket: 'podnoms-797e3.appspot.com', - messagingSenderId: '777042345082' + apiKey: 'AIzaSyA5pGl4o1oGJi1Ke-842Lq0VvL2YZU2rfc', + authDomain: 'podnoms-api.firebaseapp.com', + databaseURL: 'https://podnoms-api.firebaseio.com', + projectId: 'podnoms-api', + storageBucket: '', + messagingSenderId: '357461672895' }), AngularFireDatabaseModule, AngularFireAuthModule, + AngularFirestoreModule, HttpClientModule, AppRoutingModule, HttpModule, @@ -207,6 +212,7 @@ export function provideConfig() { AppInsightsService, JobsService, AudioService, + FirebaseAuthService, GlobalsService ], bootstrap: [AppComponent] diff --git a/client/src/app/components/chat-widget/chat-widget.component.css b/client/src/app/components/chat-widget/chat-widget.component.css new file mode 100644 index 0000000..de7d1a5 --- /dev/null +++ b/client/src/app/components/chat-widget/chat-widget.component.css @@ -0,0 +1,230 @@ +.floated-chat-btn { + z-index: 9999; + position: fixed; + bottom: 10px; + right: 10px; + background: #097cff; + -webkit-box-shadow: 0 2px 20px 0 rgba(46, 130, 255, 0.75); + box-shadow: 0 2px 20px 0 rgba(46, 130, 255, 0.75); + border-radius: 75px; + color: #fff; + padding: 12px 20px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + vertical-align: middle; + font-size: 1.08rem; + cursor: pointer; + -webkit-transition: all 0.2s ease; + transition: all 0.2s ease; +} +.floated-chat-btn i { + vertical-align: middle; + display: inline-block; + font-size: 24px; +} +.floated-chat-btn span { + vertical-align: middle; + display: inline-block; + font-weight: 500; +} +.floated-chat-btn i + span { + margin-left: 15px; +} +.floated-chat-btn:hover { + -webkit-transform: scale(1.05); + transform: scale(1.05); + background-color: #0064d5; + -webkit-box-shadow: 0 2px 30px 0 rgba(46, 130, 255, 0.8); + box-shadow: 0 2px 30px 0 rgba(46, 130, 255, 0.8); +} +.floated-chat-w { + z-index: 9999; + position: fixed; + bottom: 70px; + right: 10px; + visibility: hidden; + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + -webkit-transition: all 0.3s ease; + transition: all 0.3s ease; +} +.floated-chat-w.active { + visibility: visible; + opacity: 1; + -webkit-transform: translateY(0px); + transform: translateY(0px); +} +.floated-chat-w .floated-chat-i { + background-color: #fff; + -webkit-box-shadow: 0 2px 40px 0 rgba(43, 132, 210, 0.41); + box-shadow: 0 2px 40px 0 rgba(43, 132, 210, 0.41); + border-radius: 10px; + width: 320px; + position: relative; +} +.floated-chat-w .floated-chat-i .chat-close { + position: absolute; + top: 10px; + right: 10px; + color: rgba(0, 0, 0, 0.8); + font-size: 10px; + cursor: pointer; +} +.floated-chat-w .chat-head { + padding: 20px; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} +.floated-chat-w .chat-head .user-w { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} +.floated-chat-w .chat-head .user-w .user-avatar-w { + width: 50px; +} +.floated-chat-w .chat-head .user-w .user-avatar-w .user-avatar { + border-radius: 40px; + overflow: hidden; +} +.floated-chat-w .chat-head .user-w .user-avatar-w .user-avatar img { + max-width: 100%; + height: auto; +} +.floated-chat-w .chat-head .user-w.with-status .user-avatar-w { + position: relative; +} +.floated-chat-w .chat-head .user-w.with-status .user-avatar-w:before { + content: ''; + width: 10px; + height: 10px; + position: absolute; + top: 2px; + right: 2px; + border-radius: 10px; + -webkit-box-shadow: 0px 0px 0px 3px #fff; + box-shadow: 0px 0px 0px 3px #fff; +} +.floated-chat-w + .chat-head + .user-w.with-status.status-green + .user-avatar-w:before { + background-color: #24b314; +} +.floated-chat-w + .chat-head + .user-w.with-status.status-red + .user-avatar-w:before { + background-color: #e65252; +} +.floated-chat-w .chat-head .user-w .user-name { + padding-left: 20px; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} +.floated-chat-w .chat-head .user-w .user-title { + margin-bottom: 2px; + color: #047bf8; +} +.floated-chat-w .chat-head .user-w .user-role { + font-weight: 500; + font-size: 0.81rem; +} +.floated-chat-w .chat-head .user-w .user-action { + width: 50px; + color: #047bf8; + font-size: 18px; +} +.floated-chat-w .chat-messages { + padding: 20px; + height: 300px; + position: relative; + overflow: hidden; +} +.floated-chat-w .chat-messages .message { + margin-bottom: 12px; +} +.floated-chat-w .chat-messages .message .message-content { + color: #594939; + padding: 10px 20px; + background-color: #fcf6ee; + border-radius: 20px 20px 20px 0px; + max-width: 80%; + display: inline-block; + text-align: left; +} +.floated-chat-w .chat-messages .message.self { + text-align: right; +} +.floated-chat-w .chat-messages .message.self .message-content { + border-radius: 20px 20px 0px 20px; + background-color: #e2efff; + color: #2a4e7f; +} +.floated-chat-w .chat-messages .date-break { + text-align: center; + margin-bottom: 10px; + color: rgba(0, 0, 0, 0.4); +} +.floated-chat-w .chat-controls { + padding: 10px; + padding-top: 0px; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} +.floated-chat-w .chat-controls .message-input { + border: 1px solid transparent; + background-color: #fff; + padding: 10px; + width: 100%; + display: block; + border-radius: 0px; +} +.floated-chat-w .chat-controls .message-input:focus { + outline: none; + border-bottom: 1px solid #047bf8; +} +.floated-chat-w .chat-controls .chat-extra { + text-align: left; + padding-left: 0px; + padding-top: 10px; +} +.floated-chat-w .chat-controls .chat-extra a { + display: inline-block; + margin-left: 10px; + font-size: 16px; + position: relative; +} +.floated-chat-w .chat-controls .chat-extra a .extra-tooltip { + background-color: rgba(0, 0, 0, 0.9); + color: #fff; + font-weight: 500; + font-size: 0.63rem; + text-transform: uppercase; + display: inline-block; + padding: 2px 7px; + border-radius: 4px; + position: absolute; + top: -20px; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + white-space: nowrap; + display: none; +} +.floated-chat-w .chat-controls .chat-extra a:hover { + text-decoration: none; +} +.floated-chat-w .chat-controls .chat-extra a:hover .extra-tooltip { + display: block; +} diff --git a/client/src/app/components/chat-widget/chat-widget.component.html b/client/src/app/components/chat-widget/chat-widget.component.html new file mode 100644 index 0000000..95087ae --- /dev/null +++ b/client/src/app/components/chat-widget/chat-widget.component.html @@ -0,0 +1,50 @@ +
+
+
+ +
+
+
+
+
+ +
+
+
+
Welly Welly Well
+
The old days are dead and gone days.
+
+
+
+
+
+
Tell me where it hurts?
+
+
+
{{message.messageText}}
+
+
+ + +
+
+
+ + Talk to us! +
diff --git a/client/src/app/components/chat-widget/chat-widget.component.ts b/client/src/app/components/chat-widget/chat-widget.component.ts new file mode 100644 index 0000000..5527938 --- /dev/null +++ b/client/src/app/components/chat-widget/chat-widget.component.ts @@ -0,0 +1,76 @@ +import { Component, OnInit } from '@angular/core'; +import { AngularFireAuth } from 'angularfire2/auth'; +import { FirebaseAuthService } from '../../services/firebase-auth.service'; +import { + AngularFirestore, + AngularFirestoreCollection, + AngularFirestoreDocument +} from 'angularfire2/firestore'; +import { Observable } from 'rxjs/Observable'; +import { ProfileService } from '../../services/profile.service'; +import { FirebaseUser } from '../../models/firebaseuser.model'; + +interface ChatMessage { + messageText: string; + name: string; + fromUser: string; +} +@Component({ + selector: 'app-chat-widget', + templateUrl: './chat-widget.component.html', + styleUrls: ['./chat-widget.component.css'] +}) +export class ChatWidgetComponent implements OnInit { + chatActive: boolean = false; + loading: boolean = false; + currentUser: FirebaseUser; + + messageCollection: AngularFirestoreCollection; + messages: Observable; + userName: string = 'Anonymous User'; + constructor( + private _firebaseAuthService: FirebaseAuthService, + private _profileService: ProfileService, + private afs: AngularFirestore + ) { + this._profileService + .getProfile() + .subscribe((p) => (this.userName = p.firstName + ' ' + p.lastName)); + } + + ngOnInit() {} + + togglePopup() { + this.chatActive = !this.chatActive; + if (this.chatActive) { + this.loading = true; + this._firebaseAuthService.user.subscribe((u) => { + if (u === null) { + this._firebaseAuthService + .anonymousLogin() + .then((user) => this._openChat(user)); + } else { + this._openChat(u); + } + }); + } + } + _openChat(user) { + this.currentUser = user; + this.loading = false; + this.messageCollection = this.afs.collection('supportchat'); + this.messages = this.messageCollection.valueChanges(); + } + + sendMessage(el: HTMLInputElement) { + if (el.value) { + this.afs + .collection('supportchat') + .add({ + fromUid: this.currentUser.uid, + messageText: el.value, + name: this.currentUser.displayName + }); + } + } +} diff --git a/client/src/app/models/firebaseuser.model.ts b/client/src/app/models/firebaseuser.model.ts new file mode 100644 index 0000000..3c17450 --- /dev/null +++ b/client/src/app/models/firebaseuser.model.ts @@ -0,0 +1,6 @@ +export interface FirebaseUser { + uid: string; + email?: string | null; + photoURL?: string; + displayName?: string; +} diff --git a/client/src/app/services/firebase-auth.service.ts b/client/src/app/services/firebase-auth.service.ts new file mode 100644 index 0000000..c33fb81 --- /dev/null +++ b/client/src/app/services/firebase-auth.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@angular/core'; +import { AngularFireAuth } from 'angularfire2/auth'; +import { AngularFireDatabase } from 'angularfire2/database'; +import { + AngularFirestore, + AngularFirestoreDocument +} from 'angularfire2/firestore'; +import { Observable } from 'rxjs/Observable'; +import { FirebaseUser } from 'app/models/firebaseuser.model'; + +@Injectable() +export class FirebaseAuthService { + user: Observable; + + constructor( + private afAuth: AngularFireAuth, + private afs: AngularFirestore + ) { + this.user = this.afAuth.authState.switchMap((user) => { + if (user) { + return this.afs + .doc(`users/${user.uid}`) + .valueChanges(); + } else { + return Observable.of(null); + } + }); + } + + private handleError(error: Error) { + console.error(error); + } + + private updateUserData(user: FirebaseUser): Promise { + const userRef: AngularFirestoreDocument = this.afs.doc( + `users/${user.uid}` + ); + const data: FirebaseUser = { + uid: user.uid, + email: user.email || null, + displayName: user.displayName || 'nameless user', + photoURL: user.photoURL || 'https://goo.gl/Fz9nrQ' + }; + return userRef.set(data).then(() => data); + } + anonymousLogin() { + return this.afAuth.auth + .signInAnonymously() + .then((user) => { + return this.updateUserData(user); // if using firestore + }) + .catch((error) => { + console.error(error.code); + console.error(error.message); + this.handleError(error); + }); + } +} diff --git a/client/src/assets/img/alex-1.png b/client/src/assets/img/alex-1.png new file mode 100644 index 0000000..b1e9568 Binary files /dev/null and b/client/src/assets/img/alex-1.png differ