First commit
This commit is contained in:
13
tapit-frontend/.editorconfig
Normal file
13
tapit-frontend/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
27
tapit-frontend/README.md
Normal file
27
tapit-frontend/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# TapitFrontend
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.1.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||
136
tapit-frontend/angular.json
Normal file
136
tapit-frontend/angular.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"tapit-frontend": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/tapit-frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"es5BrowserSupport": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "tapit-frontend:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "tapit-frontend:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "tapit-frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tapit-frontend-e2e": {
|
||||
"root": "e2e/",
|
||||
"projectType": "application",
|
||||
"prefix": "",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "tapit-frontend:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "tapit-frontend:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": "e2e/tsconfig.e2e.json",
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "tapit-frontend"
|
||||
}
|
||||
28
tapit-frontend/e2e/protractor.conf.js
Normal file
28
tapit-frontend/e2e/protractor.conf.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.e2e.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
23
tapit-frontend/e2e/src/app.e2e-spec.ts
Normal file
23
tapit-frontend/e2e/src/app.e2e-spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Welcome to tapit-frontend!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
}));
|
||||
});
|
||||
});
|
||||
11
tapit-frontend/e2e/src/app.po.ts
Normal file
11
tapit-frontend/e2e/src/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get(browser.baseUrl) as Promise<any>;
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
||||
13
tapit-frontend/e2e/tsconfig.e2e.json
Normal file
13
tapit-frontend/e2e/tsconfig.e2e.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
10514
tapit-frontend/package-lock.json
generated
Normal file
10514
tapit-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
tapit-frontend/package.json
Normal file
48
tapit-frontend/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "tapit-frontend",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~7.2.0",
|
||||
"@angular/common": "~7.2.0",
|
||||
"@angular/compiler": "~7.2.0",
|
||||
"@angular/core": "~7.2.0",
|
||||
"@angular/forms": "~7.2.0",
|
||||
"@angular/platform-browser": "~7.2.0",
|
||||
"@angular/platform-browser-dynamic": "~7.2.0",
|
||||
"@angular/router": "~7.2.0",
|
||||
"core-js": "^2.5.4",
|
||||
"rxjs": "~6.3.3",
|
||||
"tslib": "^1.9.0",
|
||||
"zone.js": "~0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.13.0",
|
||||
"@angular/cli": "~7.3.1",
|
||||
"@angular/compiler-cli": "~7.2.0",
|
||||
"@angular/language-service": "~7.2.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~3.1.1",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~1.1.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"typescript": "~3.2.2"
|
||||
}
|
||||
}
|
||||
37
tapit-frontend/src/app/app-routing.module.ts
Normal file
37
tapit-frontend/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { MainComponent } from './main/main.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { CampaignComponent } from './campaign/campaign.component';
|
||||
import { CampaignNewComponent } from './campaign-new/campaign-new.component';
|
||||
import { CampaignViewComponent } from './campaign-view/campaign-view.component';
|
||||
import { PhonebookComponent } from './phonebook/phonebook.component';
|
||||
import { PhonebookNewComponent } from './phonebook-new/phonebook-new.component';
|
||||
import { TextTemplateComponent } from './text-template/text-template.component';
|
||||
import { TextTemplateNewComponent } from './text-template-new/text-template-new.component';
|
||||
import { ProviderComponent } from './provider/provider.component';
|
||||
import { ProfileComponent } from './profile/profile.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: MainComponent },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
{ path: 'profile', component: ProfileComponent },
|
||||
{ path: 'campaign', component: CampaignComponent },
|
||||
{ path: 'campaign/new', component: CampaignNewComponent },
|
||||
{ path: 'campaign/:id/view', component: CampaignViewComponent },
|
||||
{ path: 'phonebook', component: PhonebookComponent },
|
||||
{ path: 'phonebook/new', component: PhonebookNewComponent },
|
||||
{ path: 'phonebook/:id/edit', component: PhonebookNewComponent },
|
||||
{ path: 'text-template', component: TextTemplateComponent },
|
||||
{ path: 'text-template/new', component: TextTemplateNewComponent },
|
||||
{ path: 'text-template/:id/edit', component: TextTemplateNewComponent },
|
||||
{ path: 'provider', component: ProviderComponent },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
0
tapit-frontend/src/app/app.component.css
Normal file
0
tapit-frontend/src/app/app.component.css
Normal file
42
tapit-frontend/src/app/app.component.html
Normal file
42
tapit-frontend/src/app/app.component.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" routerLink="/">Tap It!</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav">
|
||||
<li *ngFor="let navlink of navlinks" data-toggle="collapse" data-target="#navbarNav" class="nav-item">
|
||||
<a class="nav-link" *ngIf="navlink.loginOnly === authService.loggedin" [ngClass]="{'active': router.url === navlink.link}" routerLink="/{{ navlink.link }}">
|
||||
{{ navlink.name }}
|
||||
<span *ngIf="this.router.url === navlink.link" class="sr-only">(current)</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown" *ngIf="authService.loggedin === true">
|
||||
<a class="nav-link dropdown-toggle" routerLink="{{ router.url }}" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Settings
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" routerLink="/profile">Profile</a>
|
||||
<a class="dropdown-item" routerLink="/provider">Providers</a>
|
||||
<a class="dropdown-item" routerLink="/globalsettings">Global Settings</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li *ngIf="authService.loggedin" data-toggle="collapse" data-target="#navbarNav" class="nav-item">
|
||||
<a class="nav-link" routerLink="/" (click)="authService.logout()">Log Out</a>
|
||||
</li>
|
||||
<li *ngIf="!authService.loggedin" data-toggle="collapse" data-target="#navbarNav" class="nav-item">
|
||||
<a class="nav-link" routerLink="/login">Login</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container-fluid pt-2">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
|
||||
<div class="fixed-bottom"><app-notification></app-notification></div>
|
||||
35
tapit-frontend/src/app/app.component.spec.ts
Normal file
35
tapit-frontend/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'tapit-frontend'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('tapit-frontend');
|
||||
});
|
||||
|
||||
it('should render title in a h1 tag', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to tapit-frontend!');
|
||||
});
|
||||
});
|
||||
37
tapit-frontend/src/app/app.component.ts
Normal file
37
tapit-frontend/src/app/app.component.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule, Routes, Router } from '@angular/router';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'tapit-frontend';
|
||||
navlinks = [
|
||||
{
|
||||
link: '/campaign',
|
||||
name: 'Campaigns',
|
||||
loginOnly: true,
|
||||
},
|
||||
{
|
||||
link: '/phonebook',
|
||||
name: 'Phonebook',
|
||||
loginOnly: true,
|
||||
},
|
||||
{
|
||||
link: '/text-template',
|
||||
name: 'Text Templates',
|
||||
loginOnly: true,
|
||||
},
|
||||
{
|
||||
link: '/web-template',
|
||||
name: 'Web Templates',
|
||||
loginOnly: true,
|
||||
},
|
||||
];
|
||||
constructor( private router: Router, private authService: AuthService) {
|
||||
authService.getUser();
|
||||
}
|
||||
}
|
||||
48
tapit-frontend/src/app/app.module.ts
Normal file
48
tapit-frontend/src/app/app.module.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MainComponent } from './main/main.component';
|
||||
import { CampaignComponent } from './campaign/campaign.component';
|
||||
import { CampaignNewComponent } from './campaign-new/campaign-new.component';
|
||||
import { NotificationComponent } from './notification/notification.component';
|
||||
import { PhonebookComponent } from './phonebook/phonebook.component';
|
||||
import { PhonebookNewComponent } from './phonebook-new/phonebook-new.component';
|
||||
import { TextTemplateComponent } from './text-template/text-template.component';
|
||||
import { TextTemplateNewComponent } from './text-template-new/text-template-new.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { ProviderComponent } from './provider/provider.component';
|
||||
import { ProfileComponent } from './profile/profile.component';
|
||||
import { CampaignViewComponent } from './campaign-view/campaign-view.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MainComponent,
|
||||
CampaignComponent,
|
||||
CampaignNewComponent,
|
||||
NotificationComponent,
|
||||
PhonebookComponent,
|
||||
PhonebookNewComponent,
|
||||
TextTemplateComponent,
|
||||
TextTemplateNewComponent,
|
||||
LoginComponent,
|
||||
RegisterComponent,
|
||||
ProviderComponent,
|
||||
ProfileComponent,
|
||||
CampaignViewComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
12
tapit-frontend/src/app/auth.service.spec.ts
Normal file
12
tapit-frontend/src/app/auth.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AuthService = TestBed.get(AuthService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
131
tapit-frontend/src/app/auth.service.ts
Normal file
131
tapit-frontend/src/app/auth.service.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
export class User {
|
||||
username: string;
|
||||
password: string;
|
||||
name: string;
|
||||
email: string;
|
||||
secretCode: string;
|
||||
}
|
||||
|
||||
export class UserNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: User;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
currUser = new User();
|
||||
loggedin = false;
|
||||
loginUrl = 'api/login';
|
||||
logoutUrl = 'api/logout';
|
||||
registerUrl = 'api/register';
|
||||
myselfUrl = 'api/myself';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
login(username: string, password: string) {
|
||||
this.currUser.username = username;
|
||||
this.currUser.password = password;
|
||||
this.http.post<UserNotification>(this.loginUrl, this.currUser, this.httpOptions).subscribe(usermessage => {
|
||||
if (usermessage.payload !== null) {
|
||||
this.loggedin = true;
|
||||
|
||||
// update user
|
||||
this.currUser.username = usermessage.payload.username;
|
||||
this.currUser.email = usermessage.payload.email;
|
||||
this.currUser.name = usermessage.payload.name;
|
||||
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.router.navigate(['/campaign']);
|
||||
} else {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in logging in');
|
||||
});
|
||||
this.currUser.password = '';
|
||||
}
|
||||
|
||||
register(username: string, password: string, email: string, name: string, secretCode: string) {
|
||||
this.currUser.username = username;
|
||||
this.currUser.password = password;
|
||||
this.currUser.email = email;
|
||||
this.currUser.name = name;
|
||||
this.currUser.secretCode = secretCode;
|
||||
|
||||
this.http.post<UserNotification>(this.registerUrl, this.currUser, this.httpOptions).subscribe(usermessage => {
|
||||
if (usermessage.payload !== null) {
|
||||
this.loggedin = true;
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.router.navigate(['/campaign']);
|
||||
|
||||
// update user
|
||||
this.currUser.username = usermessage.payload.username;
|
||||
this.currUser.email = usermessage.payload.email;
|
||||
this.currUser.name = usermessage.payload.name;
|
||||
} else {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
}
|
||||
});
|
||||
|
||||
this.currUser.secretCode = '';
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.http.post<UserNotification>(this.logoutUrl, '', this.httpOptions).subscribe(usermessage => {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.loggedin = false;
|
||||
this.currUser = new User();
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
}
|
||||
|
||||
getUser(): User {
|
||||
this.http.get<User>(this.myselfUrl, this.httpOptions).subscribe(thisUser => {
|
||||
this.currUser = thisUser;
|
||||
if (this.currUser.username !== '') {
|
||||
this.loggedin = true;
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
// separate one to redirect main to campaign dashboard
|
||||
if (this.router.url === '/' || this.router.url === '') {
|
||||
this.router.navigate(['/campaign']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
return this.currUser;
|
||||
}
|
||||
|
||||
getUserObs(): Observable<User> {
|
||||
return this.http.get<User>(this.myselfUrl, this.httpOptions);
|
||||
}
|
||||
|
||||
updateUser(user: User) {
|
||||
this.currUser = user;
|
||||
this.http.put<UserNotification>(this.myselfUrl, this.currUser, this.httpOptions).subscribe(usermessage => {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in updating profile');
|
||||
});
|
||||
this.currUser.password = '';
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) { }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3 mb-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Campaign Name</label>
|
||||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newCampaign.name" placeholder="Campaign Name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 mb-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="newFromNum" class="pr-2 mt-auto mb-auto">From Number</label>
|
||||
<input type="text" class="flex-grow-1" id="newFromNum" [(ngModel)]="newCampaign.fromNumber" placeholder="From Number">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add phonebook & template via list -->
|
||||
<div class="form-group">
|
||||
<label for="provider-select">Provider</label>
|
||||
<select class="form-control" [(ngModel)]="newCampaign.providerTag" id="provider-select">
|
||||
<option></option>
|
||||
<option *ngFor="let providerEnum of providerService.providerEnums" [ngValue]="providerEnum.tag">{{providerEnum.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phonebook-select">Phonebook</label>
|
||||
<select class="form-control" [(ngModel)]="newCampaign.phonebookId" id="phonebook-select">
|
||||
<option></option>
|
||||
<option *ngFor="let phonebook of phonebookService.phonebooks" [ngValue]="phonebook.id">{{phonebook.name}}: Size {{phonebook.size}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="text-template-select">Text Template</label>
|
||||
<select class="form-control" [(ngModel)]="newCampaign.textTemplateId" id="text-template-select">
|
||||
<option></option>
|
||||
<option *ngFor="let textTemplate of textTemplateService.textTemplates" [ngValue]="textTemplate.id">{{textTemplate.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitNewCampaignRun()" class="btn btn-primary mr-2">Start</button>
|
||||
<button type="button" (click)="submitNewCampaign()" class="btn btn-secondary ml-2">Save</button>
|
||||
<button type="button" *ngIf="router.url !== '/campaign/new'" (click)="askDelete()" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignNewComponent } from './campaign-new.component';
|
||||
|
||||
describe('CampaignNewComponent', () => {
|
||||
let component: CampaignNewComponent;
|
||||
let fixture: ComponentFixture<CampaignNewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CampaignNewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CampaignNewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CampaignService, Campaign } from '../campaign.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { ProviderService } from '../provider.service';
|
||||
import { PhonebookService } from '../phonebook.service';
|
||||
import { TextTemplateService } from '../text-template.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign-new',
|
||||
templateUrl: './campaign-new.component.html',
|
||||
styleUrls: ['./campaign-new.component.css']
|
||||
})
|
||||
export class CampaignNewComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private campaignService: CampaignService,
|
||||
private router: Router,
|
||||
private providerService: ProviderService,
|
||||
private phonebookService: PhonebookService,
|
||||
private textTemplateService: TextTemplateService) { }
|
||||
|
||||
newCampaign: Campaign = new Campaign();
|
||||
|
||||
submitNewCampaign() {
|
||||
this.campaignService.addCampaign(this.newCampaign);
|
||||
}
|
||||
|
||||
submitNewCampaignRun() {
|
||||
this.campaignService.addCampaignRun(this.newCampaign);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.campaign-details:read-only {
|
||||
background-color: white;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3 d-flux">
|
||||
<button type="button" *ngIf="currCampaign.currentStatus === 'Running'" (click)="pauseCampaign()" class="btn btn-warning mr-2">Pause Campaign</button>
|
||||
<button type="button" *ngIf="currCampaign.currentStatus !== 'Running'" (click)="startCampaign()" class="btn btn-primary mr-2">Start Campaign</button>
|
||||
<button type="button" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Campaign Name</span>
|
||||
</div>
|
||||
<input type="text" class="form-control campaign-details" value="{{ currCampaign.name }}" readonly>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Campaign Size</span>
|
||||
</div>
|
||||
<input type="text" class="form-control campaign-details" value="{{ currCampaign.size }}" readonly>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Campaign Status</span>
|
||||
</div>
|
||||
<input type="text" class="form-control campaign-details" value="{{ currCampaign.currentStatus }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">From</th>
|
||||
<th scope="col">To</th>
|
||||
<th scope="col">Currrent Status</th>
|
||||
<th scope="col">Time Sent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let job of currCampaign.jobs">
|
||||
<tr>
|
||||
<td>{{ job.fromNum }}</td>
|
||||
<td>{{ job.toNum }}</td>
|
||||
<td>{{ job.currentStatus }}</td>
|
||||
<td>{{ job.timeSent | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ currCampaign.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the campaign?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deleteCampaign()" data-dismiss="modal">Delete Campaign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignViewComponent } from './campaign-view.component';
|
||||
|
||||
describe('CampaignViewComponent', () => {
|
||||
let component: CampaignViewComponent;
|
||||
let fixture: ComponentFixture<CampaignViewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CampaignViewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CampaignViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CampaignService, Campaign, Job, CampaignNotification } from '../campaign.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { NotificationService } from '../notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign-view',
|
||||
templateUrl: './campaign-view.component.html',
|
||||
styleUrls: ['./campaign-view.component.css']
|
||||
})
|
||||
export class CampaignViewComponent implements OnInit {
|
||||
|
||||
currCampaign: Campaign = new Campaign();
|
||||
|
||||
id = 0;
|
||||
|
||||
constructor(
|
||||
private campaignService: CampaignService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private notificationService: NotificationService
|
||||
) { }
|
||||
|
||||
startCampaign() {
|
||||
this.campaignService.startCampaign(this.currCampaign).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.campaignService.getCampaignObs(this.id).subscribe(campaign => {
|
||||
this.currCampaign = campaign;
|
||||
});
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in starting campaign');
|
||||
});
|
||||
}
|
||||
|
||||
pauseCampaign() {
|
||||
this.campaignService.pauseCampaign(this.currCampaign).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in pausing campaign');
|
||||
});
|
||||
}
|
||||
|
||||
deleteCampaign() {
|
||||
this.campaignService.deleteCampaign(this.currCampaign);
|
||||
}
|
||||
|
||||
updateThisCampaign() {
|
||||
this.campaignService.getCampaignObs(this.id).subscribe(campaign => {
|
||||
this.currCampaign = campaign;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const idParam = 'id';
|
||||
this.route.params.subscribe( params => {
|
||||
this.id = parseInt(params[idParam], 10);
|
||||
});
|
||||
this.updateThisCampaign();
|
||||
const intervalId = setInterval(() => {
|
||||
this.updateThisCampaign();
|
||||
if (!this.router.url.includes('/campaign')) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/campaign.service.spec.ts
Normal file
12
tapit-frontend/src/app/campaign.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignService } from './campaign.service';
|
||||
|
||||
describe('CampaignService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: CampaignService = TestBed.get(CampaignService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
115
tapit-frontend/src/app/campaign.service.ts
Normal file
115
tapit-frontend/src/app/campaign.service.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class Campaign {
|
||||
id: number;
|
||||
name: string;
|
||||
fromNumber: string;
|
||||
size: number;
|
||||
currentStatus: string;
|
||||
createDate: Date;
|
||||
phonebookId: number;
|
||||
textTemplateId: number;
|
||||
webTemplateId: number;
|
||||
providerTag: string;
|
||||
jobs: Job[];
|
||||
}
|
||||
|
||||
export class Job {
|
||||
id: number;
|
||||
currentStatus: string;
|
||||
timeSent: Date;
|
||||
fromNum: string;
|
||||
toNum: string;
|
||||
}
|
||||
|
||||
export class CampaignNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: Campaign;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
||||
export class CampaignService {
|
||||
|
||||
campaigns: Campaign[] = [];
|
||||
|
||||
campaignUrl = '/api/campaign';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
getCampaigns() {
|
||||
this.http.get<Campaign[]>(this.campaignUrl).subscribe(campaigns => {
|
||||
if (campaigns === null) {
|
||||
this.campaigns = [];
|
||||
} else {
|
||||
this.campaigns = campaigns;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCampaignObs(id: number): Observable<Campaign> {
|
||||
return this.http.get<Campaign>(this.campaignUrl + '/' + id.toString());
|
||||
}
|
||||
|
||||
addCampaign(newCampaign: Campaign) {
|
||||
this.http.post<CampaignNotification>(this.campaignUrl, newCampaign, this.httpOptions).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.campaigns.push(campaignNotification.payload);
|
||||
if (campaignNotification.payload !== null) {
|
||||
this.router.navigate(['/campaign']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating template');
|
||||
});
|
||||
}
|
||||
|
||||
addCampaignRun(newCampaign: Campaign) {
|
||||
this.http.post<CampaignNotification>(this.campaignUrl, newCampaign, this.httpOptions).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.campaigns.push(campaignNotification.payload);
|
||||
if (campaignNotification.payload !== null) {
|
||||
this.startCampaign(campaignNotification.payload).subscribe();
|
||||
this.router.navigate(['/campaign']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating template');
|
||||
});
|
||||
}
|
||||
|
||||
deleteCampaign(campaign: Campaign) {
|
||||
this.http.delete<CampaignNotification>(this.campaignUrl + '/' + campaign.id.toString(), this.httpOptions)
|
||||
.subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.router.navigate(['/campaign']);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in deleting campaign');
|
||||
});
|
||||
}
|
||||
|
||||
startCampaign(campaign: Campaign) {
|
||||
return this.http.get<CampaignNotification>(this.campaignUrl + '/' + campaign.id.toString() + '/' + 'start');
|
||||
}
|
||||
|
||||
pauseCampaign(campaign: Campaign) {
|
||||
return this.http.get<CampaignNotification>(this.campaignUrl + '/' + campaign.id.toString() + '/' + 'pause');
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
|
||||
this.campaigns = [];
|
||||
this.getCampaigns();
|
||||
}
|
||||
}
|
||||
31
tapit-frontend/src/app/campaign/campaign.component.html
Normal file
31
tapit-frontend/src/app/campaign/campaign.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" routerLink="/campaign/new">New Campaign</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Target Size</th>
|
||||
<th scope="col">Create Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let campaign of campaignService.campaigns">
|
||||
<tr routerLink="/campaign/{{ campaign.id }}/view">
|
||||
<td>{{ campaign.name }}</td>
|
||||
<td>{{ campaign.currentStatus }}</td>
|
||||
<td>{{ campaign.size }}</td>
|
||||
<td>{{ campaign.createDate | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<p *ngIf="campaignService.campaigns.length === 0">No campaigns created yet. Create compaigns by clicking <a routerLink="/campaign/new">here</a></p>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
25
tapit-frontend/src/app/campaign/campaign.component.spec.ts
Normal file
25
tapit-frontend/src/app/campaign/campaign.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignComponent } from './campaign.component';
|
||||
|
||||
describe('CampaignComponent', () => {
|
||||
let component: CampaignComponent;
|
||||
let fixture: ComponentFixture<CampaignComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CampaignComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CampaignComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
24
tapit-frontend/src/app/campaign/campaign.component.ts
Normal file
24
tapit-frontend/src/app/campaign/campaign.component.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { CampaignService } from '../campaign.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign',
|
||||
templateUrl: './campaign.component.html',
|
||||
styleUrls: ['./campaign.component.css']
|
||||
})
|
||||
export class CampaignComponent implements OnInit {
|
||||
|
||||
constructor(private campaignService: CampaignService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.campaignService.getCampaigns();
|
||||
const intervalId = setInterval(() => {
|
||||
this.campaignService.getCampaigns();
|
||||
if (!this.router.url.includes('/campaign')) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}
|
||||
0
tapit-frontend/src/app/login/login.component.css
Normal file
0
tapit-frontend/src/app/login/login.component.css
Normal file
27
tapit-frontend/src/app/login/login.component.html
Normal file
27
tapit-frontend/src/app/login/login.component.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<h4>Login</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-username" class="pr-2 mt-auto mb-auto">Username</label>
|
||||
<input type="text" class="flex-grow-1" id="login-username" [(ngModel)]="username" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-password" class="pr-2 mt-auto mb-auto">Password</label>
|
||||
<input type="password" class="flex-grow-1" id="login-password" [(ngModel)]="password" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="login()" class="btn btn-primary mr-3">Login</button>
|
||||
<button type="button" (click)="routeRegister()" class="btn btn-primary">Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
25
tapit-frontend/src/app/login/login.component.spec.ts
Normal file
25
tapit-frontend/src/app/login/login.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
27
tapit-frontend/src/app/login/login.component.ts
Normal file
27
tapit-frontend/src/app/login/login.component.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthService } from '../auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.css']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
username: string;
|
||||
password: string;
|
||||
|
||||
login() {
|
||||
this.authService.login(this.username, this.password);
|
||||
}
|
||||
|
||||
routeRegister() {
|
||||
this.router.navigate(['/register']);
|
||||
}
|
||||
constructor(private authService: AuthService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
0
tapit-frontend/src/app/main/main.component.css
Normal file
0
tapit-frontend/src/app/main/main.component.css
Normal file
3
tapit-frontend/src/app/main/main.component.html
Normal file
3
tapit-frontend/src/app/main/main.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<p>
|
||||
main works!
|
||||
</p>
|
||||
25
tapit-frontend/src/app/main/main.component.spec.ts
Normal file
25
tapit-frontend/src/app/main/main.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MainComponent } from './main.component';
|
||||
|
||||
describe('MainComponent', () => {
|
||||
let component: MainComponent;
|
||||
let fixture: ComponentFixture<MainComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MainComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MainComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
tapit-frontend/src/app/main/main.component.ts
Normal file
15
tapit-frontend/src/app/main/main.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main',
|
||||
templateUrl: './main.component.html',
|
||||
styleUrls: ['./main.component.css']
|
||||
})
|
||||
export class MainComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/notification.service.spec.ts
Normal file
12
tapit-frontend/src/app/notification.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
describe('NotificationService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: NotificationService = TestBed.get(NotificationService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
38
tapit-frontend/src/app/notification.service.ts
Normal file
38
tapit-frontend/src/app/notification.service.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export class Notification {
|
||||
id: number;
|
||||
resultType: string; // enum success or failure or info
|
||||
text: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
notifications: Notification[] = [];
|
||||
currentCount = 0;
|
||||
|
||||
addNotification(resultType, text) {
|
||||
const newNotification = new Notification();
|
||||
newNotification.id = this.currentCount;
|
||||
this.currentCount++;
|
||||
newNotification.resultType = resultType;
|
||||
newNotification.text = text;
|
||||
|
||||
this.notifications.push(newNotification);
|
||||
setTimeout(() => this.closeNotification(newNotification), 3000);
|
||||
}
|
||||
|
||||
closeNotification(notify: Notification) {
|
||||
for (let i = 0; i < this.notifications.length; i++) {
|
||||
if (this.notifications[i].id === notify.id) {
|
||||
this.notifications.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="alert notification col-11 mx-auto" *ngFor="let notification of notificationService.notifications" [ngClass]="{'alert-success': notification.resultType === 'success', 'alert-danger': notification.resultType ==='failure'}" (click)=notificationService.closeNotification(notification)>
|
||||
{{ notification.text }}
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationComponent } from './notification.component';
|
||||
|
||||
describe('NotificationComponent', () => {
|
||||
let component: NotificationComponent;
|
||||
let fixture: ComponentFixture<NotificationComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ NotificationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotificationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NotificationService } from '../notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-notification',
|
||||
templateUrl: './notification.component.html',
|
||||
styleUrls: ['./notification.component.css']
|
||||
})
|
||||
export class NotificationComponent implements OnInit {
|
||||
|
||||
constructor(private notificationService: NotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.no-space-break {
|
||||
white-space:nowrap;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Phonebook Name</label>
|
||||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newPhonebook.name" placeholder="Phonebook Name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label class="no-space-break mt-auto mb-auto pr-2" for="import-records">Import Records</label>
|
||||
<div class="custom-file" id="import-records">
|
||||
<input type="file" (change)="importPhoneRecords($event.target.files)" class="custom-file-input" id="customFile">
|
||||
<label class="custom-file-label" for="customFile">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex">
|
||||
<p><small><em><a href="/assets/phonebook-template.xlsx">Download file template here.</a></em></small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-1">
|
||||
<div class="col-12 d-flex">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">First Name</th>
|
||||
<th scope="col">Last Name</th>
|
||||
<th scope="col">Alias</th>
|
||||
<th scope="col">Phone Number</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let phoneRecord of newPhoneRecords">
|
||||
<tr>
|
||||
<td>{{ phoneRecord.firstName }}</td>
|
||||
<td>{{ phoneRecord.lastName }}</td>
|
||||
<td>{{ phoneRecord.alias }}</td>
|
||||
<td>{{ phoneRecord.phoneNumber }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<tr (keyup.enter)="insertAdditionalRecord()">
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.firstName" class="form-control" placeholder="firstName"></td>
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.lastName" class="form-control" placeholder="lastName"></td>
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.alias" class="form-control" placeholder="alias"></td>
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.phoneNumber" class="form-control" placeholder="phoneNumber"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<p><small><em>Press enter to insert additional record</em></small></p>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitNewPhonebook()" class="btn btn-primary mr-2">Save Phonebook</button>
|
||||
<button type="button" *ngIf="router.url !== '/phonebook/new'" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ newPhonebook.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the phonebook?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deletePhonebook()" data-dismiss="modal">Delete Phonebook</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PhonebookNewComponent } from './phonebook-new.component';
|
||||
|
||||
describe('PhonebookNewComponent', () => {
|
||||
let component: PhonebookNewComponent;
|
||||
let fixture: ComponentFixture<PhonebookNewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PhonebookNewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PhonebookNewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PhonebookService, Phonebook, PhoneRecord } from '../phonebook.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-phonebook-new',
|
||||
templateUrl: './phonebook-new.component.html',
|
||||
styleUrls: ['./phonebook-new.component.css']
|
||||
})
|
||||
export class PhonebookNewComponent implements OnInit {
|
||||
|
||||
constructor(private phonebookService: PhonebookService, private router: Router, private route: ActivatedRoute) { }
|
||||
|
||||
id = 0;
|
||||
|
||||
newPhonebook: Phonebook = new Phonebook();
|
||||
newPhoneRecords: PhoneRecord[] = [];
|
||||
additionalRecord: PhoneRecord = new PhoneRecord();
|
||||
|
||||
insertAdditionalRecord() {
|
||||
this.newPhoneRecords = this.newPhoneRecords.concat(this.additionalRecord);
|
||||
this.additionalRecord = new PhoneRecord();
|
||||
this.additionalRecord.phoneNumber = '';
|
||||
}
|
||||
|
||||
importPhoneRecords(files: FileList) {
|
||||
this.phonebookService.uploadPhonebook(files.item(0)).subscribe(data => {
|
||||
this.newPhoneRecords = this.newPhoneRecords.concat(data);
|
||||
});
|
||||
}
|
||||
|
||||
submitNewPhonebook() {
|
||||
if (this.router.url === '/phonebook/new') {
|
||||
if (this.additionalRecord.phoneNumber !== '') {
|
||||
this.insertAdditionalRecord();
|
||||
}
|
||||
this.newPhonebook.records = this.newPhoneRecords;
|
||||
this.phonebookService.addPhonebook(this.newPhonebook);
|
||||
} else {
|
||||
this.editPhonebook();
|
||||
}
|
||||
}
|
||||
|
||||
deletePhonebook() {
|
||||
this.phonebookService.deletePhonebook(this.newPhonebook);
|
||||
}
|
||||
|
||||
editPhonebook() {
|
||||
this.newPhonebook.records = this.newPhoneRecords;
|
||||
this.phonebookService.editPhonebook(this.newPhonebook);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.additionalRecord = new PhoneRecord();
|
||||
this.additionalRecord.phoneNumber = '';
|
||||
|
||||
// if page is edit
|
||||
if (this.router.url !== '/phonebook/new') {
|
||||
const idParam = 'id';
|
||||
this.route.params.subscribe( params => {
|
||||
this.id = parseInt(params[idParam], 10);
|
||||
this.phonebookService.getPhonebookObs(this.id).subscribe(currPb => {
|
||||
this.newPhonebook = currPb;
|
||||
this.newPhoneRecords = this.newPhonebook.records;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/phonebook.service.spec.ts
Normal file
12
tapit-frontend/src/app/phonebook.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PhonebookService } from './phonebook.service';
|
||||
|
||||
describe('PhonebookService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: PhonebookService = TestBed.get(PhonebookService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
105
tapit-frontend/src/app/phonebook.service.ts
Normal file
105
tapit-frontend/src/app/phonebook.service.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class Phonebook {
|
||||
id: number;
|
||||
name: string;
|
||||
size: number;
|
||||
createDate: Date;
|
||||
records: PhoneRecord[];
|
||||
}
|
||||
|
||||
export class PhoneRecord {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
alias: string;
|
||||
phoneNumber: string;
|
||||
}
|
||||
|
||||
export class PhonebookNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: Phonebook;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PhonebookService {
|
||||
|
||||
phonebooks: Phonebook[] = [];
|
||||
|
||||
phonebookUrl = '/api/phonebook';
|
||||
phonebookImportUrl = '/api/import-phonebook';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
getPhonebooks() {
|
||||
this.http.get<Phonebook[]>(this.phonebookUrl).subscribe(phonebooks => {
|
||||
if (phonebooks === null) {
|
||||
this.phonebooks = [];
|
||||
} else {
|
||||
this.phonebooks = phonebooks;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPhonebookObs(id: number): Observable<Phonebook> {
|
||||
return this.http.get<Phonebook>(this.phonebookUrl + '/' + id.toString());
|
||||
}
|
||||
|
||||
addPhonebook(phonebook: Phonebook) {
|
||||
this.http.post<PhonebookNotification>(this.phonebookUrl, phonebook, this.httpOptions).subscribe(pbNotification => {
|
||||
this.notificationService.addNotification(pbNotification.resultType, pbNotification.text);
|
||||
this.phonebooks.push(pbNotification.payload);
|
||||
if (pbNotification.payload !== null) {
|
||||
this.router.navigate(['/phonebook']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating phonebook');
|
||||
});
|
||||
}
|
||||
|
||||
editPhonebook(phonebook: Phonebook) {
|
||||
this.http.put<PhonebookNotification>(this.phonebookUrl + '/' + phonebook.id.toString(), phonebook, this.httpOptions)
|
||||
.subscribe(pbNotification => {
|
||||
this.notificationService.addNotification(pbNotification.resultType, pbNotification.text);
|
||||
if (pbNotification.payload !== null) {
|
||||
this.router.navigate(['/phonebook']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in editing phonebook');
|
||||
});
|
||||
}
|
||||
|
||||
deletePhonebook(phonebook: Phonebook) {
|
||||
this.http.delete<PhonebookNotification>(this.phonebookUrl + '/' + phonebook.id.toString(), this.httpOptions)
|
||||
.subscribe(pbNotification => {
|
||||
this.notificationService.addNotification(pbNotification.resultType, pbNotification.text);
|
||||
this.router.navigate(['/phonebook']);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in deleting phonebook');
|
||||
});
|
||||
}
|
||||
|
||||
uploadPhonebook(file: File): Observable<PhoneRecord[]> {
|
||||
const formData = new FormData();
|
||||
formData.append('phonebookFile', file);
|
||||
return this.http.post<PhoneRecord[]>(this.phonebookImportUrl, formData);
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
|
||||
this.getPhonebooks();
|
||||
}
|
||||
}
|
||||
29
tapit-frontend/src/app/phonebook/phonebook.component.html
Normal file
29
tapit-frontend/src/app/phonebook/phonebook.component.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" routerLink="/phonebook/new">New Phonebook</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Phonebook Size</th>
|
||||
<th scope="col">Create Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let phonebook of phonebookService.phonebooks">
|
||||
<tr routerLink="/phonebook/{{ phonebook.id }}/edit">
|
||||
<td>{{ phonebook.name }}</td>
|
||||
<td>{{ phonebook.size }}</td>
|
||||
<td>{{ phonebook.createDate | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<p *ngIf="phonebookService.phonebooks.length === 0">No phonebooks created yet. Create phonebooks by clicking <a routerLink="/phonebook/new">here</a></p>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
25
tapit-frontend/src/app/phonebook/phonebook.component.spec.ts
Normal file
25
tapit-frontend/src/app/phonebook/phonebook.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PhonebookComponent } from './phonebook.component';
|
||||
|
||||
describe('PhonebookComponent', () => {
|
||||
let component: PhonebookComponent;
|
||||
let fixture: ComponentFixture<PhonebookComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PhonebookComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PhonebookComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
17
tapit-frontend/src/app/phonebook/phonebook.component.ts
Normal file
17
tapit-frontend/src/app/phonebook/phonebook.component.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PhonebookService } from '../phonebook.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-phonebook',
|
||||
templateUrl: './phonebook.component.html',
|
||||
styleUrls: ['./phonebook.component.css']
|
||||
})
|
||||
export class PhonebookComponent implements OnInit {
|
||||
|
||||
constructor(private phonebookService: PhonebookService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.phonebookService.getPhonebooks();
|
||||
}
|
||||
|
||||
}
|
||||
32
tapit-frontend/src/app/profile/profile.component.html
Normal file
32
tapit-frontend/src/app/profile/profile.component.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<h4>Settings for {{ currUser.username }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="setting-displayname" class="pr-2 mt-auto mb-auto">Display Name</label>
|
||||
<input type="text" class="flex-grow-1" id="setting-displayname" [(ngModel)]="currUser.name" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="setting-email" class="pr-2 mt-auto mb-auto">Email</label>
|
||||
<input type="text" class="flex-grow-1" id="setting-email" [(ngModel)]="currUser.email" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="setting-password" class="pr-2 mt-auto mb-auto">Change Password</label>
|
||||
<input type="password" class="flex-grow-1" id="setting-password" [(ngModel)]="currUser.password" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="updateUser()" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
25
tapit-frontend/src/app/profile/profile.component.spec.ts
Normal file
25
tapit-frontend/src/app/profile/profile.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfileComponent } from './profile.component';
|
||||
|
||||
describe('ProfileComponent', () => {
|
||||
let component: ProfileComponent;
|
||||
let fixture: ComponentFixture<ProfileComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ProfileComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
24
tapit-frontend/src/app/profile/profile.component.ts
Normal file
24
tapit-frontend/src/app/profile/profile.component.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { AuthService, User } from '../auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrls: ['./profile.component.css']
|
||||
})
|
||||
export class ProfileComponent implements OnInit {
|
||||
|
||||
constructor(private authService: AuthService) { }
|
||||
currUser: User;
|
||||
|
||||
updateUser() {
|
||||
this.authService.updateUser(this.currUser);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.authService.getUserObs().subscribe(user => {
|
||||
this.currUser = JSON.parse(JSON.stringify(user));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/provider.service.spec.ts
Normal file
12
tapit-frontend/src/app/provider.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProviderService } from './provider.service';
|
||||
|
||||
describe('ProviderService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: ProviderService = TestBed.get(ProviderService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
58
tapit-frontend/src/app/provider.service.ts
Normal file
58
tapit-frontend/src/app/provider.service.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
export class TwilioProvider {
|
||||
accountSID: string;
|
||||
authToken: string;
|
||||
}
|
||||
|
||||
export class TwilioProviderNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: TwilioProvider;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ProviderService {
|
||||
|
||||
twilioProviderSettings: TwilioProvider = new TwilioProvider();
|
||||
twilioUrl = '/api/provider/twilio';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
providerEnums = [
|
||||
{name: 'Twilio', tag: 'twilio'},
|
||||
];
|
||||
|
||||
getTwilioProvider() {
|
||||
this.http.get<TwilioProvider>(this.twilioUrl, this.httpOptions).subscribe(thisTwilio => {
|
||||
this.twilioProviderSettings = thisTwilio;
|
||||
});
|
||||
}
|
||||
|
||||
getTwilioProviderObs(): Observable<TwilioProvider> {
|
||||
return this.http.get<TwilioProvider>(this.twilioUrl, this.httpOptions);
|
||||
}
|
||||
|
||||
updateTwilioProvider(tProvider: TwilioProvider) {
|
||||
this.http.post<TwilioProviderNotification>(this.twilioUrl, tProvider, this.httpOptions).subscribe(usermessage => {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.twilioProviderSettings = usermessage.payload;
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in updating Twilio provider');
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private notificationService: NotificationService) {
|
||||
this.getTwilioProvider();
|
||||
}
|
||||
}
|
||||
28
tapit-frontend/src/app/provider/provider.component.html
Normal file
28
tapit-frontend/src/app/provider/provider.component.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Twilio Settings
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="form-group col-12">
|
||||
<label for="twilio-account-sid" class="pr-2 mt-auto mb-auto">Account SID</label>
|
||||
<input type="text" class="flex-grow-1" id="twilio-account-sid" [(ngModel)]="currTwilioProvider.accountSID" placeholder="Twilio Account SID">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-12">
|
||||
<label for="twilio-auth-token" class="pr-2 mt-auto mb-auto">Auth Token</label>
|
||||
<input type="text" class="flex-grow-1" id="twilio-auth-token" [(ngModel)]="currTwilioProvider.authToken" placeholder="Twilio Auth Token">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitProviders()" class="btn btn-primary ml-2">Save Provider Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
25
tapit-frontend/src/app/provider/provider.component.spec.ts
Normal file
25
tapit-frontend/src/app/provider/provider.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProviderComponent } from './provider.component';
|
||||
|
||||
describe('ProviderComponent', () => {
|
||||
let component: ProviderComponent;
|
||||
let fixture: ComponentFixture<ProviderComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ProviderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProviderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
26
tapit-frontend/src/app/provider/provider.component.ts
Normal file
26
tapit-frontend/src/app/provider/provider.component.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ProviderService, TwilioProvider } from '../provider.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-provider',
|
||||
templateUrl: './provider.component.html',
|
||||
styleUrls: ['./provider.component.css']
|
||||
})
|
||||
export class ProviderComponent implements OnInit {
|
||||
|
||||
currTwilioProvider: TwilioProvider = new TwilioProvider();
|
||||
|
||||
submitProviders() {
|
||||
this.providerService.updateTwilioProvider(this.currTwilioProvider);
|
||||
}
|
||||
|
||||
constructor(private providerService: ProviderService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.providerService.getTwilioProviderObs().subscribe(currTwilio => {
|
||||
this.currTwilioProvider = currTwilio;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
44
tapit-frontend/src/app/register/register.component.html
Normal file
44
tapit-frontend/src/app/register/register.component.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<h4>Login</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-name" class="pr-2 mt-auto mb-auto">Name</label>
|
||||
<input type="text" class="flex-grow-1" id="login-name" [(ngModel)]="name" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-username" class="pr-2 mt-auto mb-auto">Username</label>
|
||||
<input type="text" class="flex-grow-1" id="register-username" [(ngModel)]="username" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-password" class="pr-2 mt-auto mb-auto">Password</label>
|
||||
<input type="password" class="flex-grow-1" id="register-password" [(ngModel)]="password" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-username" class="pr-2 mt-auto mb-auto">Email</label>
|
||||
<input type="text" class="flex-grow-1" id="register-email" [(ngModel)]="email" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="secret-code" class="pr-2 mt-auto mb-auto">Secret Code</label>
|
||||
<input type="text" class="flex-grow-1" id="secret-code" [(ngModel)]="secretCode" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="register()" class="btn btn-primary">Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
25
tapit-frontend/src/app/register/register.component.spec.ts
Normal file
25
tapit-frontend/src/app/register/register.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RegisterComponent } from './register.component';
|
||||
|
||||
describe('RegisterComponent', () => {
|
||||
let component: RegisterComponent;
|
||||
let fixture: ComponentFixture<RegisterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ RegisterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RegisterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
25
tapit-frontend/src/app/register/register.component.ts
Normal file
25
tapit-frontend/src/app/register/register.component.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthService } from '../auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.css']
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
username = '';
|
||||
password = '';
|
||||
email = '';
|
||||
name = '';
|
||||
secretCode = '';
|
||||
|
||||
register() {
|
||||
this.authService.register(this.username, this.password, this.email, this.name, this.secretCode);
|
||||
}
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#new-text-preview {
|
||||
font-family: "Courier New"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Text Template Name</label>
|
||||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newTextTemplate.name" placeholder="Text Template Name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="form-group col-12">
|
||||
<label for="text-template-area">Text Template</label>
|
||||
<textarea class="form-control flex" [(ngModel)]="newTextTemplate.templateStr" (ngModelChange)="updatePreview()" id="text-template-area" rows="6"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitNewTextTemplate()" class="btn btn-primary ml-2">Save Text Template</button>
|
||||
<button type="button" *ngIf="router.url !== '/text-template/new'" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<div class="form-group col-12">
|
||||
<label for="tags-available" class="pr-2 mt-auto mb-auto">Tags Available</label>
|
||||
<div id="tags-available">
|
||||
<ul>
|
||||
<li>{{ '{' }}firstName{{ '}' }}</li>
|
||||
<li>{{ '{' }}lastName{{ '}' }}</li>
|
||||
<li>{{ '{' }}alias{{ '}' }}</li>
|
||||
<li>{{ '{' }}phoneNumber{{ '}' }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<div class="form-group col-12">
|
||||
<label for="new-text-preview" class="pr-2 mt-auto mb-auto">Preview</label>
|
||||
<textarea class="form-control flex" [(ngModel)]="previewStr" id="new-text-preview" rows="6" disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ newTextTemplate.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the text template?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deleteTextTemplate()" data-dismiss="modal">Delete Text Template</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TextTemplateNewComponent } from './text-template-new.component';
|
||||
|
||||
describe('TextTemplateNewComponent', () => {
|
||||
let component: TextTemplateNewComponent;
|
||||
let fixture: ComponentFixture<TextTemplateNewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TextTemplateNewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TextTemplateNewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { TextTemplate, TextTemplateService } from '../text-template.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-text-template-new',
|
||||
templateUrl: './text-template-new.component.html',
|
||||
styleUrls: ['./text-template-new.component.css']
|
||||
})
|
||||
export class TextTemplateNewComponent implements OnInit {
|
||||
|
||||
newTextTemplate: TextTemplate = new TextTemplate();
|
||||
previewStr: string;
|
||||
id = 0;
|
||||
|
||||
submitNewTextTemplate() {
|
||||
if (this.router.url === '/text-template/new') {
|
||||
this.textTemplateService.addTextTemplate(this.newTextTemplate);
|
||||
} else {
|
||||
this.editTextTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
updatePreview() {
|
||||
let tempStr = '';
|
||||
tempStr = this.newTextTemplate.templateStr;
|
||||
tempStr = tempStr.replace('{firstName}', 'John');
|
||||
tempStr = tempStr.replace('{lastName}', 'Smith');
|
||||
tempStr = tempStr.replace('{alias}', 'Johnny');
|
||||
tempStr = tempStr.replace('{phoneNumber}', '+6598765432');
|
||||
|
||||
this.previewStr = tempStr;
|
||||
}
|
||||
|
||||
deleteTextTemplate() {
|
||||
this.textTemplateService.deleteTextTemplate(this.newTextTemplate);
|
||||
}
|
||||
|
||||
editTextTemplate() {
|
||||
this.textTemplateService.editTextTemplate(this.newTextTemplate);
|
||||
}
|
||||
|
||||
constructor(private textTemplateService: TextTemplateService, private router: Router, private route: ActivatedRoute) { }
|
||||
|
||||
ngOnInit() {
|
||||
// if page is edit
|
||||
if (this.router.url !== '/text-template/new') {
|
||||
const idParam = 'id';
|
||||
this.route.params.subscribe( params => {
|
||||
this.id = parseInt(params[idParam], 10);
|
||||
this.textTemplateService.getTextTemplateObs(this.id).subscribe(currTT => {
|
||||
this.newTextTemplate = currTT;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/text-template.service.spec.ts
Normal file
12
tapit-frontend/src/app/text-template.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TextTemplateService } from './text-template.service';
|
||||
|
||||
describe('TextTemplateService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: TextTemplateService = TestBed.get(TextTemplateService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
89
tapit-frontend/src/app/text-template.service.ts
Normal file
89
tapit-frontend/src/app/text-template.service.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class TextTemplate {
|
||||
id: number;
|
||||
name: string;
|
||||
templateStr: string;
|
||||
createDate: Date;
|
||||
}
|
||||
|
||||
export class TextTemplateNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: TextTemplate;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
||||
export class TextTemplateService {
|
||||
|
||||
textTemplates: TextTemplate[] = [];
|
||||
|
||||
templateUrl = '/api/text-template';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
getTextTemplates() {
|
||||
this.http.get<TextTemplate[]>(this.templateUrl).subscribe(templates => {
|
||||
if (templates === null) {
|
||||
this.textTemplates = [];
|
||||
} else {
|
||||
this.textTemplates = templates;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTextTemplateObs(id: number) {
|
||||
return this.http.get<TextTemplate>(this.templateUrl + '/' + id.toString());
|
||||
}
|
||||
|
||||
addTextTemplate(newTextTemplate: TextTemplate) {
|
||||
this.http.post<TextTemplateNotification>(this.templateUrl, newTextTemplate, this.httpOptions).subscribe(templateNotification => {
|
||||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
|
||||
this.textTemplates.push(templateNotification.payload);
|
||||
if (templateNotification.payload !== null) {
|
||||
this.router.navigate(['/text-template']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating template');
|
||||
});
|
||||
}
|
||||
|
||||
deleteTextTemplate(textTemplate: TextTemplate) {
|
||||
this.http.delete<TextTemplateNotification>(this.templateUrl + '/' + textTemplate.id.toString(), this.httpOptions)
|
||||
.subscribe(templateNotification => {
|
||||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
|
||||
this.router.navigate(['/text-template']);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in deleting text template');
|
||||
});
|
||||
}
|
||||
|
||||
editTextTemplate(textTemplate: TextTemplate) {
|
||||
this.http.put<TextTemplateNotification>(this.templateUrl + '/' + textTemplate.id.toString(), textTemplate, this.httpOptions)
|
||||
.subscribe(templateNotification => {
|
||||
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
|
||||
if (templateNotification.payload !== null) {
|
||||
this.router.navigate(['/text-template']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in editing text template');
|
||||
});
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
|
||||
this.getTextTemplates();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" routerLink="/text-template/new">New Text Template</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Create Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let textTemplate of textTemplateService.textTemplates">
|
||||
<tr routerLink="/text-template/{{ textTemplate.id }}/edit">
|
||||
<td>{{ textTemplate.name }}</td>
|
||||
<td>{{ textTemplate.createDate | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<p *ngIf="textTemplateService.textTemplates.length === 0">No text template created yet. Create templates by clicking <a routerLink="/text-template/new">here</a></p>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TextTemplateComponent } from './text-template.component';
|
||||
|
||||
describe('TextTemplateComponent', () => {
|
||||
let component: TextTemplateComponent;
|
||||
let fixture: ComponentFixture<TextTemplateComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TextTemplateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TextTemplateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { TextTemplateService } from '../text-template.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-text-template',
|
||||
templateUrl: './text-template.component.html',
|
||||
styleUrls: ['./text-template.component.css']
|
||||
})
|
||||
export class TextTemplateComponent implements OnInit {
|
||||
|
||||
constructor(private textTemplateService: TextTemplateService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.textTemplateService.getTextTemplates();
|
||||
}
|
||||
|
||||
}
|
||||
3
tapit-frontend/src/app/urls.ts
Normal file
3
tapit-frontend/src/app/urls.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export class Urls {
|
||||
getCampaigns = 'http://127.0.0.1:8000/api/campaigns';
|
||||
}
|
||||
0
tapit-frontend/src/assets/.gitkeep
Normal file
0
tapit-frontend/src/assets/.gitkeep
Normal file
3719
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.css
vendored
Normal file
3719
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.min.css
vendored
Normal file
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
331
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.css
vendored
Normal file
331
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.css
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="reset"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
File diff suppressed because one or more lines are too long
8
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.min.css
vendored
Normal file
8
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.min.css
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||
File diff suppressed because one or more lines are too long
10038
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap.css
vendored
Normal file
10038
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css
vendored
Normal file
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7013
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.js
vendored
Normal file
7013
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.min.js
vendored
Normal file
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4435
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.js
vendored
Normal file
4435
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js
vendored
Normal file
7
tapit-frontend/src/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user