first commit

This commit is contained in:
wilfried 2021-05-27 10:53:34 +02:00
parent 6984c550fb
commit 595d0e3013
47 changed files with 13471 additions and 0 deletions

View file

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {AppComponent} from "./app.component";
import {LoginComponent} from "./login/login.component";
import {PrivateComponent} from "./private/private.component";
const routes: Routes = [
{
path: 'login',
component: LoginComponent,
},
{
path: 'private',
component: PrivateComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View file

@ -0,0 +1 @@
<router-outlet></router-outlet>

View file

View file

@ -0,0 +1,35 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'frontend'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('frontend');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('frontend app is running!');
});
});

View file

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'frontend';
}

View file

@ -0,0 +1,35 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {HttpClientModule} from "@angular/common/http";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { GeneralComponent } from './general/general.component';
import { PrivateComponent } from './private/private.component';
import { NavbarComponent } from './navbar/navbar.component';
import {CommonModule} from "@angular/common";
@NgModule({
declarations: [
AppComponent,
LoginComponent,
GeneralComponent,
PrivateComponent,
NavbarComponent
],
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
CommonModule,
ReactiveFormsModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View file

@ -0,0 +1 @@
<p>general works!</p>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GeneralComponent } from './general.component';
describe('GeneralComponent', () => {
let component: GeneralComponent;
let fixture: ComponentFixture<GeneralComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ GeneralComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(GeneralComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-general',
templateUrl: './general.component.html',
styleUrls: ['./general.component.scss']
})
export class GeneralComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View file

@ -0,0 +1,23 @@
<div class="container">
<div class="modal-header">
<h4 class="modal-title">Login</h4>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<input type="text"
placeholder="Enter your phone number"
class="form-control"
[(ngModel)]="phone">
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-sm btn-primary" (click) = "login()">Login</button>
</div>
</div>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,50 @@
import { Component, OnInit } from '@angular/core';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {Router} from "@angular/router";
import {AuthService} from "../services/auth/auth.service";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
public phone = '';
constructor(
private auth: AuthService,
private router: Router
) {
}
ngOnInit(): void {
}
login() : void {
console.log('1 -Phone :', this.phone);
if (this.auth.sendAuthentication(this.phone) === true){
this.router.navigateByUrl('/private');
} else {
console.log("error");
}
}
}

View file

@ -0,0 +1 @@
<p>navbar works!</p>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
describe('NavbarComponent', () => {
let component: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NavbarComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(NavbarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View file

@ -0,0 +1,57 @@
<div class="container-fluid" *ngIf="showScreen">
<div class="row">
<div class="col-md-4">
<div class="user-list-card">
<div class="user-card"
[ngClass]="{'active' : user?.phone === selectedUser?.phone}"
*ngFor="let user of userList"
(click)="selectUserHandler(user.phone)"
>
<!---->
<img src="./assets/image/user.png" height="25" width="25"/>
<p class="username">{{user?.name}}</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="chat-container">
<ng-container *ngIf="selectedUser">
<div class="chat-header">
<p class="username">{{selectedUser?.name}}</p>
</div>
<div class="chat-body">
<div *ngFor="let item of messageArray"
[ngClass]="{'same-user' : item?.user === currentUser?.name}"
>
<!---->
<p class="message-container">{{item?.message}}</p>
</div>
</div>
<div class="chat-footer">
<div class="row">
<div class="col-md-10">
<div class="form-group mb-0">
<input type="text" placeholder="Type a message" class="form-control"
[(ngModel)]="messageText" (keyup)="$event.keyCode === 13 && sendMessage()"/>
</div>
</div>
<div class="col-md-2 text-center align-self-center">
<button class="btn btn-primary btn-bm px-3 " (click)="sendMessage()">Send</button>
</div>
</div>
</div>
</ng-container>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PrivateComponent } from './private.component';
describe('PrivateComponent', () => {
let component: PrivateComponent;
let fixture: ComponentFixture<PrivateComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PrivateComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PrivateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,143 @@
import {NgModule, Component, OnInit} from '@angular/core';
import {ChatService} from "../services/chat/chat.service";
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
@Component({
selector: 'app-private',
templateUrl: './private.component.html',
styleUrls: ['./private.component.scss']
})
export class PrivateComponent implements OnInit {
public userList = [
{
id: 1,
name: 'Yuki Vachot',
phone: '0608020103',
roomId: {
2: 'room-1',
3: 'room-2',
4: 'room-3'
}
},
{
id: 2,
name: 'Wilfried Vallee',
phone: '0604080701',
roomId: {
1: 'room-1',
3: 'room-4',
4: 'room-5'
}
},
{
id: 3,
name: 'Khai Phan',
phone: '0603050960',
roomId: {
1: 'room-2',
2: 'room-4',
4: 'room-6'
}
}
];
// @ts-ignore
public roomId: string;
// @ts-ignore
public messageText: string;
public messageArray: {user: string, message: string}[] = [];
private storageArray = [];
// @ts-ignore
public showScreen: boolean;
// @ts-ignore
public phone: string;
// @ts-ignore
public currentUser;
// @ts-ignore
public selectedUser;
constructor(
private chatService: ChatService,
) {
}
ngOnInit(): void {
this.chatService.getMessage()
.subscribe((data: {user: string, message: string}) => {
this.messageArray.push(data);
if (this.roomId) {
setTimeout( () => {
this.storageArray = this.chatService.getStorage();
//@ts-ignore
const storeIndex = this.storageArray.findIndex((storage) => storage.roomId === this.roomId);
//@ts-ignore
this.messageArray = this.storageArray[storeIndex].chats;
}, 500);
}
});
}
selectUserHandler(phone: string): void {
this.selectedUser = this.userList.find(user => user.phone === phone);
this.roomId = this.selectedUser.roomId[this.currentUser.id];
this.messageArray = [];
this.storageArray = this.chatService.getStorage();
// @ts-ignore
const storeIndex = this.storageArray.findIndex((storage) => storage.roomId === this.roomId);
if (storeIndex > -1) {
// @ts-ignore
this.messageArray = this.storageArray[storeIndex].chats;
}
this.join(this.currentUser.name, this.roomId);
}
join(username: string, roomId: string): void {
this.chatService.joinRoom({user: username, room: roomId});
}
sendMessage(): void {
this.chatService.sendMessage({
user: this.currentUser.name,
room: this.roomId,
message: this.messageText
});
this.storageArray = this.chatService.getStorage();
// @ts-ignore
const storeIndex = this.storageArray.findIndex((storage) => storage.roomId === this.roomId);
if (storeIndex > -1) {
// @ts-ignore
this.storageArray[storeIndex].chats.push({
user: this.currentUser.name,
message: this.messageText
})
} else {
const updateStorage = {
roomId: this.roomId,
chats: [{
user: this.currentUser.name,
message: this.messageText
}]
};
// @ts-ignore
this.storageArray.push(updateStorage);
}
this.chatService.setStorage(this.storageArray);
this.messageText = '';
}
}

View file

@ -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();
});
});

View file

@ -0,0 +1,79 @@
import { Injectable } from '@angular/core';
import {Observable} from "rxjs";
@Injectable({
providedIn: 'root'
})
export class AuthService {
// @ts-ignore
// @ts-ignore
public roomId: string;
// @ts-ignore
public messageText: string;
public messageArray: {user: string, message: string}[] = [];
private storageArray = [];
// @ts-ignore
public showScreen: boolean;
// @ts-ignore
public phone: string;
// @ts-ignore
public currentUser;
// @ts-ignore
public selectedUser;
public userList = [
{
id: 1,
name: 'Yuki Vachot',
phone: '0608020103',
roomId: {
2: 'room-1',
3: 'room-2',
4: 'room-3'
}
},
{
id: 2,
name: 'Wilfried Vallee',
phone: '0604080701',
roomId: {
1: 'room-1',
3: 'room-4',
4: 'room-5'
}
},
{
id: 3,
name: 'Khai Phan',
phone: '0603050960',
roomId: {
1: 'room-2',
2: 'room-4',
4: 'room-6'
}
}
];
constructor() {}
sendAuthentication(phone: string): boolean {
this.phone = phone;
this.currentUser = this.userList.find(user => user.phone === this.phone.toString());
this.userList = this.userList.filter((user) => user.phone !== this.phone.toString());
//console.log("2 -", this.currentUser.phone);
console.log("3 -", this.phone);
if(this.currentUser) {
this.showScreen = true;
console.log("user : TRUE", this.phone.toString());
} else {
console.log("error user", this.phone.toString());
}
return this.showScreen ;
}
}

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ChatService } from './chat.service';
describe('ChatService', () => {
let service: ChatService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ChatService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { io, Socket } from "socket.io-client";
import {Observable} from "rxjs";
@Injectable({
providedIn: 'root'
})
export class ChatService {
private socket: Socket;
private url = 'http://localhost:3000';
constructor() {
this.socket = io(this.url);
}
// @ts-ignore
joinRoom(data): void {
this.socket.emit('join', data);
}
// @ts-ignore
sendMessage(data): void{
this.socket.emit('message', data);
}
getMessage(): Observable<any> {
return new Observable<{user: string, message: string}>(observer => {
this.socket.on('new message', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
}
});
}
getStorage() {
const storage = localStorage.getItem('chats');
return storage ? JSON.parse(storage) : [];
}
setStorage(data: any) {
localStorage.setItem('chats', JSON.stringify(data));
}
}

View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View file

@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

BIN
frontend/src/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

13
frontend/src/index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Frontend</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

12
frontend/src/main.ts Normal file
View file

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

65
frontend/src/polyfills.ts Normal file
View file

@ -0,0 +1,65 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* IE11 requires the following for NgClass support on SVG elements
*/
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
import '@angular/localize/init';
/***************************************************************************************************
* APPLICATION IMPORTS
*/

109
frontend/src/styles.scss Normal file
View file

@ -0,0 +1,109 @@
body {
background-color: #E5E5E5;
}
.container-fluid {
padding: 50px 200px;
overflow: hidden;
}
.user-list-card {
background-color: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
height: calc(100vh - 100px);
padding: 10px;
.user-card {
display: flex;
padding: 10px;
cursor: pointer;
&.active {
background-color: #E5E5E5;
}
.username {
font-size: 20px;
font-weight: 500;
margin-bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
}
.user-card:not(:last-child) {
border-bottom: 1px solid #BBBBBB;
}
}
.chat-container {
background-color: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
height: calc(100vh - 100px);
position: relative;
overflow: hidden;
.chat-header {
height: 70px;
background-color: #E5E5E5;
display: flex;
justify-content: flex-start;
align-items: center;
.username {
font-size: 14px;
font-weight: 500;
margin-bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
}
.chat-body {
background-image: url(./assets/image/user.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
height: calc(100vh - 125px);
overflow-y: auto;
.message-container {
background-color: white;
padding: 7px;
border-radius: 5px;
width: fit-content;
max-width: 90%;
margin-bottom: 15px;
}
.same-user {
display: flex;
justify-content: flex-end;
.message-container {
background-color: lightskyblue;
}
}
}
.chat-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: #EDEDED;
padding: 10px 20px;
.form-control {
background-color: white;
border: 1px solid #D8DDEC;
box-sizing: border-box;
font-size: 1rem;
color: black;
}
}
}

25
frontend/src/test.ts Normal file
View file

@ -0,0 +1,25 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);