WIP: Integrate app with API
This commit is contained in:
parent
c5a4f19fc2
commit
09e32d1cd1
50 changed files with 743 additions and 168 deletions
37
.vscode/launch.json
vendored
Normal file
37
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Launch index.html",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "launch",
|
||||||
|
"reAttach": true,
|
||||||
|
"file": "${workspaceFolder}/index.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch localhost",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "launch",
|
||||||
|
"reAttach": true,
|
||||||
|
"url": "http://localhost/index.html",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "attach"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch addon",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "launch",
|
||||||
|
"reAttach": true,
|
||||||
|
"addonType": "webExtension",
|
||||||
|
"addonPath": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
src/app/account.ts
Normal file
7
src/app/account.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { IAccount } from './budget-database'
|
||||||
|
|
||||||
|
export class Account implements IAccount {
|
||||||
|
id: number;
|
||||||
|
remoteId: number;
|
||||||
|
name: string;
|
||||||
|
}
|
15
src/app/accounts.service.spec.ts
Normal file
15
src/app/accounts.service.spec.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AccountsService } from './accounts.service';
|
||||||
|
|
||||||
|
describe('AccountsService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [AccountsService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([AccountsService], (service: AccountsService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
9
src/app/accounts.service.ts
Normal file
9
src/app/accounts.service.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AccountsService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
}
|
4
src/app/actionable.ts
Normal file
4
src/app/actionable.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface Actionable {
|
||||||
|
doAction(): void;
|
||||||
|
getActionLabel(): string;
|
||||||
|
}
|
0
src/app/add-edit-account/add-edit-account.component.css
Normal file
0
src/app/add-edit-account/add-edit-account.component.css
Normal file
3
src/app/add-edit-account/add-edit-account.component.html
Normal file
3
src/app/add-edit-account/add-edit-account.component.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<p>
|
||||||
|
add-edit-account works!
|
||||||
|
</p>
|
25
src/app/add-edit-account/add-edit-account.component.spec.ts
Normal file
25
src/app/add-edit-account/add-edit-account.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddEditAccountComponent } from './add-edit-account.component';
|
||||||
|
|
||||||
|
describe('AddEditAccountComponent', () => {
|
||||||
|
let component: AddEditAccountComponent;
|
||||||
|
let fixture: ComponentFixture<AddEditAccountComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ AddEditAccountComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AddEditAccountComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
15
src/app/add-edit-account/add-edit-account.component.ts
Normal file
15
src/app/add-edit-account/add-edit-account.component.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-edit-account',
|
||||||
|
templateUrl: './add-edit-account.component.html',
|
||||||
|
styleUrls: ['./add-edit-account.component.css']
|
||||||
|
})
|
||||||
|
export class AddEditAccountComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,16 +1,3 @@
|
||||||
.category-form {
|
|
||||||
padding: 1em;
|
|
||||||
color: #F1F1F1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-form * {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
mat-radio-button {
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-delete {
|
.button-delete {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
<mat-toolbar>
|
|
||||||
<span>
|
|
||||||
<a mat-button (click)="goBack()">
|
|
||||||
<mat-icon>arrow_back</mat-icon>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{{ title }}
|
|
||||||
</span>
|
|
||||||
<span class="action-item">
|
|
||||||
<a mat-button (click)="save()">Save</a>
|
|
||||||
</span>
|
|
||||||
</mat-toolbar>
|
|
||||||
<div *ngIf="!currentCategory">
|
<div *ngIf="!currentCategory">
|
||||||
<p>Select a category from the list to view details about it or edit it.</p>
|
<p>Select a category from the list to view details about it or edit it.</p>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="currentCategory" class="category-form">
|
<div *ngIf="currentCategory" class="form category-form">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput [(ngModel)]="currentCategory.name" placeholder="Name" required>
|
<input matInput [(ngModel)]="currentCategory.name" placeholder="Name" required>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
|
@ -1,31 +1,36 @@
|
||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
|
||||||
import { CategoryService } from '../category.service'
|
import { CategoryService } from '../category.service'
|
||||||
import { Category } from '../category'
|
import { Category } from '../category'
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
|
import { Actionable } from '../actionable';
|
||||||
|
import { AppComponent } from '../app.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-edit-category',
|
selector: 'app-add-edit-category',
|
||||||
templateUrl: './add-edit-category.component.html',
|
templateUrl: './add-edit-category.component.html',
|
||||||
styleUrls: ['./add-edit-category.component.css']
|
styleUrls: ['./add-edit-category.component.css']
|
||||||
})
|
})
|
||||||
export class AddEditCategoryComponent implements OnInit {
|
export class AddEditCategoryComponent implements OnInit, Actionable, OnDestroy {
|
||||||
|
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() currentCategory: Category;
|
@Input() currentCategory: Category;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
private categoryService: CategoryService,
|
private categoryService: CategoryService,
|
||||||
private location: Location
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.app.actionable = this;
|
||||||
|
this.app.backEnabled = true;
|
||||||
|
this.app.title = this.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack(): void {
|
ngOnDestroy() {
|
||||||
this.location.back()
|
this.app.actionable = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
doAction(): void {
|
||||||
if (this.currentCategory.id) {
|
if (this.currentCategory.id) {
|
||||||
// This is an existing category, update it
|
// This is an existing category, update it
|
||||||
this.categoryService.updateCategory(this.currentCategory);
|
this.categoryService.updateCategory(this.currentCategory);
|
||||||
|
@ -33,11 +38,15 @@ export class AddEditCategoryComponent implements OnInit {
|
||||||
// This is a new category, save it
|
// This is a new category, save it
|
||||||
this.categoryService.saveCategory(this.currentCategory);
|
this.categoryService.saveCategory(this.currentCategory);
|
||||||
}
|
}
|
||||||
this.goBack()
|
this.app.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionLabel(): string {
|
||||||
|
return 'Save';
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
this.categoryService.deleteCategory(this.currentCategory);
|
this.categoryService.deleteCategory(this.currentCategory);
|
||||||
this.goBack()
|
this.app.goBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
<mat-toolbar>
|
<div [hidden]="currentTransaction">
|
||||||
<span>
|
|
||||||
<a mat-button (click)="goBack()">
|
|
||||||
<mat-icon>arrow_back</mat-icon>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{{ title }}
|
|
||||||
</span>
|
|
||||||
<span class="action-item">
|
|
||||||
<a mat-button (click)="save()">Save</a>
|
|
||||||
</span>
|
|
||||||
</mat-toolbar>
|
|
||||||
<div *ngIf="!currentTransaction">
|
|
||||||
<p>Select a transaction from the list to view details about it or edit it.</p>
|
<p>Select a transaction from the list to view details about it or edit it.</p>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="currentTransaction" class="transaction-form">
|
<div [hidden]="!currentTransaction" class="form transaction-form">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
|
<input matInput [(ngModel)]="currentTransaction.title" placeholder="Name" required>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
|
@ -1,39 +1,42 @@
|
||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||||
import { Transaction } from '../transaction'
|
import { Transaction } from '../transaction'
|
||||||
import { TransactionType } from '../transaction.type'
|
import { TransactionType } from '../transaction.type'
|
||||||
import { TransactionService } from '../transaction.service'
|
import { TransactionService } from '../transaction.service'
|
||||||
import { Location } from '@angular/common';
|
import { Category } from '../category'
|
||||||
import { Category } from '../category'
|
|
||||||
import { CategoryService } from '../category.service'
|
import { CategoryService } from '../category.service'
|
||||||
|
import { AppComponent } from '../app.component';
|
||||||
|
import { Actionable } from '../actionable';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-edit-transaction',
|
selector: 'app-add-edit-transaction',
|
||||||
templateUrl: './add-edit-transaction.component.html',
|
templateUrl: './add-edit-transaction.component.html',
|
||||||
styleUrls: ['./add-edit-transaction.component.css']
|
styleUrls: ['./add-edit-transaction.component.css']
|
||||||
})
|
})
|
||||||
export class AddEditTransactionComponent implements OnInit {
|
export class AddEditTransactionComponent implements OnInit, OnDestroy, Actionable {
|
||||||
|
|
||||||
@Input() title: string;
|
@Input() title: string;
|
||||||
@Input() currentTransaction: Transaction;
|
@Input() currentTransaction: Transaction;
|
||||||
public transactionType = TransactionType;
|
public transactionType = TransactionType;
|
||||||
public selectedCategory: Category;
|
public selectedCategory: Category;
|
||||||
public categories: Category[]
|
public categories: Category[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
private categoryService: CategoryService,
|
private categoryService: CategoryService,
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private location: Location
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getCategories()
|
this.app.title = this.title;
|
||||||
|
this.app.backEnabled = true;
|
||||||
|
this.app.actionable = this;
|
||||||
|
this.getCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack(): void {
|
ngOnDestroy() {
|
||||||
this.location.back()
|
this.app.actionable = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
doAction(): void {
|
||||||
if (this.currentTransaction.id) {
|
if (this.currentTransaction.id) {
|
||||||
// This is an existing transaction, update it
|
// This is an existing transaction, update it
|
||||||
this.transactionService.updateTransaction(this.currentTransaction);
|
this.transactionService.updateTransaction(this.currentTransaction);
|
||||||
|
@ -41,15 +44,19 @@ export class AddEditTransactionComponent implements OnInit {
|
||||||
// This is a new transaction, save it
|
// This is a new transaction, save it
|
||||||
this.transactionService.saveTransaction(this.currentTransaction);
|
this.transactionService.saveTransaction(this.currentTransaction);
|
||||||
}
|
}
|
||||||
this.goBack()
|
this.app.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionLabel(): string {
|
||||||
|
return 'Save';
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(): void {
|
delete(): void {
|
||||||
this.transactionService.deleteTransaction(this.currentTransaction);
|
this.transactionService.deleteTransaction(this.currentTransaction);
|
||||||
this.goBack()
|
this.app.goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategories() {
|
getCategories() {
|
||||||
this.categoryService.getCategories().subscribe(categories => this.categories = categories)
|
this.categoryService.getCategories().subscribe(categories => this.categories = categories);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/app/api.service.spec.ts
Normal file
19
src/app/api.service.spec.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
|
describe('ApiService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientModule,
|
||||||
|
],
|
||||||
|
providers: [ApiService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([ApiService], (service: ApiService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
52
src/app/api.service.ts
Normal file
52
src/app/api.service.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
|
const httpOptions = {
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}),
|
||||||
|
withCredentials: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const host = 'http://localhost:9090';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ApiService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
login(username: string, password: string): Observable<any> {
|
||||||
|
return this.http.post(
|
||||||
|
host + '/login',
|
||||||
|
{
|
||||||
|
'username': username,
|
||||||
|
'password': password
|
||||||
|
},
|
||||||
|
httpOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(username: string, email: string, password: string): Observable<any> {
|
||||||
|
return this.http.post(
|
||||||
|
host + '/register',
|
||||||
|
{
|
||||||
|
'username': username,
|
||||||
|
'email': email,
|
||||||
|
'password': password
|
||||||
|
},
|
||||||
|
httpOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): Observable<any> {
|
||||||
|
return this.http.get(
|
||||||
|
host + '/logout',
|
||||||
|
httpOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,16 +7,20 @@ import { NewTransactionComponent } from './new-transaction/new-transaction.compo
|
||||||
import { CategoriesComponent } from './categories/categories.component';
|
import { CategoriesComponent } from './categories/categories.component';
|
||||||
import { CategoryDetailsComponent } from './category-details/category-details.component';
|
import { CategoryDetailsComponent } from './category-details/category-details.component';
|
||||||
import { NewCategoryComponent } from './new-category/new-category.component';
|
import { NewCategoryComponent } from './new-category/new-category.component';
|
||||||
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { RegisterComponent } from './register/register.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: DashboardComponent },
|
{ path: '', component: DashboardComponent },
|
||||||
|
{ path: 'login', component: LoginComponent },
|
||||||
|
{ path: 'register', component: RegisterComponent },
|
||||||
{ path: 'transactions', component: TransactionsComponent },
|
{ path: 'transactions', component: TransactionsComponent },
|
||||||
{ path: 'transactions/new', component: NewTransactionComponent },
|
{ path: 'transactions/new', component: NewTransactionComponent },
|
||||||
{ path: 'transactions/:id', component: TransactionDetailsComponent },
|
{ path: 'transactions/:id', component: TransactionDetailsComponent },
|
||||||
{ path: 'categories', component: CategoriesComponent },
|
{ path: 'categories', component: CategoriesComponent },
|
||||||
{ path: 'categories/new', component: NewCategoryComponent },
|
{ path: 'categories/new', component: NewCategoryComponent },
|
||||||
{ path: 'categories/:id', component: CategoryDetailsComponent },
|
{ path: 'categories/:id', component: CategoryDetailsComponent },
|
||||||
]
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
|
@ -1 +1,29 @@
|
||||||
<router-outlet></router-outlet>
|
<mat-sidenav-container class="sidenav-container">
|
||||||
|
<mat-sidenav #sidenav mode="over" closed>
|
||||||
|
<mat-nav-list (click)="sidenav.close()">
|
||||||
|
<a mat-list-item routerLink="/accounts">Manage Accounts</a>
|
||||||
|
<a mat-list-item *ngIf="!authService.currentUser" routerLink="/login">Login</a>
|
||||||
|
<a mat-list-item *ngIf="!authService.currentUser" routerLink="/register">Register</a>
|
||||||
|
<a mat-list-item *ngIf="authService.currentUser" (click)="logout()">Logout</a>
|
||||||
|
</mat-nav-list>
|
||||||
|
</mat-sidenav>
|
||||||
|
<mat-sidenav-content>
|
||||||
|
<mat-toolbar>
|
||||||
|
<span>
|
||||||
|
<a mat-button [hidden]="!backEnabled" (click)="goBack()">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</a>
|
||||||
|
<a mat-button [hidden]="backEnabled" (click)="sidenav.open()">
|
||||||
|
<mat-icon>menu</mat-icon>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ title }}
|
||||||
|
</span>
|
||||||
|
<span class="action-item">
|
||||||
|
<a mat-button *ngIf="actionable" (click)="actionable.doAction()">{{ actionable.getActionLabel() }}</a>
|
||||||
|
</span>
|
||||||
|
</mat-toolbar>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</mat-sidenav-content>
|
||||||
|
</mat-sidenav-container>
|
|
@ -1,4 +1,7 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Actionable } from './actionable';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
@ -6,5 +9,20 @@ import { Component } from '@angular/core';
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'budget';
|
public title = 'Budget';
|
||||||
|
public backEnabled = false;
|
||||||
|
public actionable: Actionable;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public authService: AuthService,
|
||||||
|
private location: Location
|
||||||
|
) { }
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,17 @@ import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import {
|
import {
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDatepickerModule,
|
MatDatepickerModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatListModule,
|
MatListModule,
|
||||||
MatRadioModule,
|
MatRadioModule,
|
||||||
MatProgressBarModule,
|
MatProgressBarModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
|
MatSidenavModule,
|
||||||
} from '@angular/material';
|
} from '@angular/material';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
@ -29,6 +30,12 @@ import { NewCategoryComponent } from './new-category/new-category.component';
|
||||||
import { CategoryListComponent } from './category-list/category-list.component';
|
import { CategoryListComponent } from './category-list/category-list.component';
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
|
import { LoginComponent } from './login/login.component';
|
||||||
|
import { RegisterComponent } from './register/register.component';
|
||||||
|
import { AddEditAccountComponent } from './add-edit-account/add-edit-account.component';
|
||||||
|
import { EditProfileComponent } from './edit-profile/edit-profile.component';
|
||||||
|
import { UserComponent } from './user/user.component';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -42,7 +49,12 @@ import { environment } from '../environments/environment';
|
||||||
CategoryDetailsComponent,
|
CategoryDetailsComponent,
|
||||||
AddEditCategoryComponent,
|
AddEditCategoryComponent,
|
||||||
NewCategoryComponent,
|
NewCategoryComponent,
|
||||||
CategoryListComponent
|
CategoryListComponent,
|
||||||
|
LoginComponent,
|
||||||
|
RegisterComponent,
|
||||||
|
AddEditAccountComponent,
|
||||||
|
EditProfileComponent,
|
||||||
|
UserComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -57,9 +69,11 @@ import { environment } from '../environments/environment';
|
||||||
MatProgressBarModule,
|
MatProgressBarModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
|
MatSidenavModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
|
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||||
|
HttpClientModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
|
21
src/app/auth.service.spec.ts
Normal file
21
src/app/auth.service.spec.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
|
describe('AuthService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientModule,
|
||||||
|
RouterTestingModule,
|
||||||
|
],
|
||||||
|
providers: [AuthService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([AuthService], (service: AuthService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
55
src/app/auth.service.ts
Normal file
55
src/app/auth.service.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { User } from './user';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthService {
|
||||||
|
private currentUser: User;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
private router: Router,
|
||||||
|
) {
|
||||||
|
console.log('AuthService constructed');
|
||||||
|
}
|
||||||
|
|
||||||
|
login(user: User) {
|
||||||
|
this.apiService.login(user.name, user.password).subscribe(
|
||||||
|
value => {
|
||||||
|
this.currentUser = value;
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log('Login failed');
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(user: User) {
|
||||||
|
this.apiService.register(user.name, user.email, user.password).subscribe(
|
||||||
|
value => {
|
||||||
|
this.login(value);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log('Registration failed');
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.apiService.logout().subscribe(
|
||||||
|
value => {
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log('Logout failed');
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +1,67 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import Dexie from 'dexie';
|
import Dexie from 'dexie';
|
||||||
|
import { Account } from './account'
|
||||||
import { Category } from './category'
|
import { Category } from './category'
|
||||||
import { Transaction } from './transaction'
|
import { Transaction } from './transaction'
|
||||||
import { TransactionType } from './transaction.type';
|
import { TransactionType } from './transaction.type';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class BudgetDatabase extends Dexie {
|
export class BudgetDatabase extends Dexie {
|
||||||
transactions: Dexie.Table<ITransaction, number>;
|
transactions: Dexie.Table<ITransaction, number>;
|
||||||
categories: Dexie.Table<ICategory, number>;
|
categories: Dexie.Table<ICategory, number>;
|
||||||
|
accounts: Dexie.Table<IAccount, number>;
|
||||||
|
|
||||||
constructor () {
|
constructor() {
|
||||||
super('BudgetDatabase')
|
super('BudgetDatabase')
|
||||||
this.version(1).stores({
|
this.version(1).stores({
|
||||||
transactions: `++id, title, description, amount, date, category, type`,
|
transactions: `++id, title, description, amount, date, category, type`,
|
||||||
categories: `++id, name, amount, repeat, color`
|
categories: `++id, name, amount, repeat, color`
|
||||||
})
|
})
|
||||||
this.version(2).stores({
|
this.version(2).stores({
|
||||||
transactions: `++id, title, description, amount, date, category_id, type`,
|
transactions: `++id, title, description, amount, date, category_id, type`,
|
||||||
categories: `++id, name, amount, repeat, color, type`
|
categories: `++id, name, amount, repeat, color, type`
|
||||||
})
|
})
|
||||||
this.version(3).stores({
|
this.version(3).stores({
|
||||||
transactions: `++id, title, description, amount, date, category_id, type`,
|
transactions: `++id, title, description, amount, date, category_id, type`,
|
||||||
categories: `++id, name, amount, repeat, color`
|
categories: `++id, name, amount, repeat, color`
|
||||||
})
|
})
|
||||||
this.transactions.mapToClass(Transaction)
|
this.version(4).stores({
|
||||||
this.categories.mapToClass(Category)
|
transactions: `++id, remote_id, account_id, title, description, amount, date, category_id, type`,
|
||||||
}
|
categories: `++id, remote_id, account_id, name, amount, repeat, color`,
|
||||||
|
accounts: `++id, remote_id, name`
|
||||||
|
})
|
||||||
|
this.transactions.mapToClass(Transaction)
|
||||||
|
this.categories.mapToClass(Category)
|
||||||
|
this.accounts.mapToClass(Account)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITransaction {
|
export interface ITransaction {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
accountId: number;
|
||||||
description: string;
|
remoteId: number;
|
||||||
amount: number;
|
title: string;
|
||||||
date: Date;
|
description: string;
|
||||||
categoryId: number;
|
amount: number;
|
||||||
type: TransactionType;
|
date: Date;
|
||||||
|
categoryId: number;
|
||||||
|
type: TransactionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICategory{
|
export interface ICategory {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
accountId: number;
|
||||||
amount: number;
|
remoteId: number;
|
||||||
repeat: string;
|
name: string;
|
||||||
color: string;
|
amount: number;
|
||||||
|
repeat: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAccount {
|
||||||
|
id: number;
|
||||||
|
remoteId: number;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
<mat-toolbar>
|
|
||||||
<span>
|
|
||||||
<a mat-button (click)="goBack()">
|
|
||||||
<mat-icon>arrow_back</mat-icon>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span>Categories</span>
|
|
||||||
<!-- empty span object for spacing -->
|
|
||||||
<span></span>
|
|
||||||
</mat-toolbar>
|
|
||||||
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
<app-category-list [categories]="categories" [categoryBalances]="categoryBalances"></app-category-list>
|
||||||
<a mat-fab routerLink="/categories/new">
|
<a mat-fab routerLink="/categories/new">
|
||||||
<mat-icon aria-label="Add">add</mat-icon>
|
<mat-icon aria-label="Add">add</mat-icon>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { CategoryService } from '../category.service'
|
import { CategoryService } from '../category.service';
|
||||||
import { Category } from '../category'
|
import { Category } from '../category';
|
||||||
import { Location } from '@angular/common';
|
import { AppComponent } from '../app.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-categories',
|
selector: 'app-categories',
|
||||||
|
@ -14,25 +14,22 @@ export class CategoriesComponent implements OnInit {
|
||||||
public categoryBalances: Map<number, number>;
|
public categoryBalances: Map<number, number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
private categoryService: CategoryService,
|
private categoryService: CategoryService,
|
||||||
private location: Location
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.app.title = 'Categories';
|
||||||
this.getCategories();
|
this.getCategories();
|
||||||
this.categoryBalances = new Map();
|
this.categoryBalances = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategories(): void {
|
getCategories(): void {
|
||||||
this.categoryService.getCategories().subscribe(categories => {
|
this.categoryService.getCategories().subscribe(categories => {
|
||||||
this.categories = categories
|
this.categories = categories;
|
||||||
for (let category of this.categories) {
|
for (const category of this.categories) {
|
||||||
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
|
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
goBack(): void {
|
|
||||||
this.location.back()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { ICategory } from './budget-database'
|
||||||
|
|
||||||
export class Category implements ICategory {
|
export class Category implements ICategory {
|
||||||
id: number;
|
id: number;
|
||||||
|
accountId: number;
|
||||||
|
remoteId: number;
|
||||||
name: string;
|
name: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
repeat: string;
|
repeat: string;
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
<mat-toolbar>
|
|
||||||
<!-- empty span object for spacing -->
|
|
||||||
<span></span>
|
|
||||||
<span>My Finances</span>
|
|
||||||
<!-- empty span object for spacing -->
|
|
||||||
<span></span>
|
|
||||||
</mat-toolbar>
|
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<div class="dashboard-primary">
|
<div class="dashboard-primary">
|
||||||
<h2 class="balance">
|
<h2 class="balance">
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Transaction } from '../transaction'
|
||||||
import { TransactionService } from '../transaction.service'
|
import { TransactionService } from '../transaction.service'
|
||||||
import { CategoryService } from '../category.service'
|
import { CategoryService } from '../category.service'
|
||||||
import { Category } from '../category'
|
import { Category } from '../category'
|
||||||
|
import { AppComponent } from '../app.component';
|
||||||
|
import { AuthService } from '../auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
|
@ -17,11 +19,14 @@ export class DashboardComponent implements OnInit {
|
||||||
categoryBalances: Map<number, number>;
|
categoryBalances: Map<number, number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private categoryService: CategoryService
|
private categoryService: CategoryService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.app.backEnabled = false;
|
||||||
|
this.app.title = 'My Finances';
|
||||||
this.balance = 0;
|
this.balance = 0;
|
||||||
this.getBalance();
|
this.getBalance();
|
||||||
this.getTransactions();
|
this.getTransactions();
|
||||||
|
@ -39,10 +44,10 @@ export class DashboardComponent implements OnInit {
|
||||||
|
|
||||||
getCategories(): void {
|
getCategories(): void {
|
||||||
this.categoryService.getCategories(5).subscribe(categories => {
|
this.categoryService.getCategories(5).subscribe(categories => {
|
||||||
this.categories = categories
|
this.categories = categories;
|
||||||
for (let category of this.categories) {
|
for (const category of this.categories) {
|
||||||
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
|
this.categoryService.getBalance(category).subscribe(balance => this.categoryBalances.set(category.id, balance))
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
0
src/app/edit-profile/edit-profile.component.css
Normal file
0
src/app/edit-profile/edit-profile.component.css
Normal file
3
src/app/edit-profile/edit-profile.component.html
Normal file
3
src/app/edit-profile/edit-profile.component.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<p>
|
||||||
|
edit-profile works!
|
||||||
|
</p>
|
25
src/app/edit-profile/edit-profile.component.spec.ts
Normal file
25
src/app/edit-profile/edit-profile.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EditProfileComponent } from './edit-profile.component';
|
||||||
|
|
||||||
|
describe('EditProfileComponent', () => {
|
||||||
|
let component: EditProfileComponent;
|
||||||
|
let fixture: ComponentFixture<EditProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ EditProfileComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(EditProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
15
src/app/edit-profile/edit-profile.component.ts
Normal file
15
src/app/edit-profile/edit-profile.component.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-edit-profile',
|
||||||
|
templateUrl: './edit-profile.component.html',
|
||||||
|
styleUrls: ['./edit-profile.component.css']
|
||||||
|
})
|
||||||
|
export class EditProfileComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
src/app/login/login.component.css
Normal file
0
src/app/login/login.component.css
Normal file
8
src/app/login/login.component.html
Normal file
8
src/app/login/login.component.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="form login-form">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="Username" [(ngModel)]="user.name" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="password" placeholder="Password" [(ngModel)]="user.password" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
25
src/app/login/login.component.spec.ts
Normal file
25
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();
|
||||||
|
});
|
||||||
|
});
|
38
src/app/login/login.component.ts
Normal file
38
src/app/login/login.component.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { Component, OnInit, OnDestroy, OnChanges } from '@angular/core';
|
||||||
|
import { AuthService } from '../auth.service';
|
||||||
|
import { User } from '../user';
|
||||||
|
import { Actionable } from '../actionable';
|
||||||
|
import { AppComponent } from '../app.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.css']
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit, OnDestroy, Actionable {
|
||||||
|
|
||||||
|
public user: User = new User();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
|
private authService: AuthService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.app.title = 'Login';
|
||||||
|
this.app.actionable = this;
|
||||||
|
this.app.backEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.app.actionable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
doAction(): void {
|
||||||
|
this.authService.login(this.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionLabel() {
|
||||||
|
return 'Submit';
|
||||||
|
}
|
||||||
|
}
|
0
src/app/register/register.component.css
Normal file
0
src/app/register/register.component.css
Normal file
14
src/app/register/register.component.html
Normal file
14
src/app/register/register.component.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="form register-form">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="Username" [(ngModel)]="user.name" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="email" placeholder="Email Address" [(ngModel)]="user.email" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="password" placeholder="Password" [(ngModel)]="user.password" />
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="password" placeholder="Confirm Password" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
25
src/app/register/register.component.spec.ts
Normal file
25
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();
|
||||||
|
});
|
||||||
|
});
|
38
src/app/register/register.component.ts
Normal file
38
src/app/register/register.component.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { User } from '../user';
|
||||||
|
import { AppComponent } from '../app.component';
|
||||||
|
import { AuthService } from '../auth.service';
|
||||||
|
import { Actionable } from '../actionable';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-register',
|
||||||
|
templateUrl: './register.component.html',
|
||||||
|
styleUrls: ['./register.component.css']
|
||||||
|
})
|
||||||
|
export class RegisterComponent implements OnInit, OnDestroy, Actionable {
|
||||||
|
|
||||||
|
public user: User = new User();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
|
private authService: AuthService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.app.title = 'Login';
|
||||||
|
this.app.actionable = this;
|
||||||
|
this.app.backEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.app.actionable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
doAction(): void {
|
||||||
|
this.authService.register(this.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionLabel() {
|
||||||
|
return 'Submit';
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import { TransactionType } from './transaction.type';
|
||||||
|
|
||||||
export class Transaction implements ITransaction {
|
export class Transaction implements ITransaction {
|
||||||
id: number;
|
id: number;
|
||||||
|
accountId: number;
|
||||||
|
remoteId: number;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
<mat-toolbar>
|
|
||||||
<span>
|
|
||||||
<a mat-button (click)="goBack()">
|
|
||||||
<mat-icon>arrow_back</mat-icon>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span>Transactions</span>
|
|
||||||
<!-- empty span object for spacing -->
|
|
||||||
<span></span>
|
|
||||||
</mat-toolbar>
|
|
||||||
<mat-nav-list *ngIf="transactions" class="transactions">
|
<mat-nav-list *ngIf="transactions" class="transactions">
|
||||||
<a mat-list-item *ngFor="let transaction of transactions" routerLink="/transactions/{{ transaction.id }}">
|
<a mat-list-item *ngFor="let transaction of transactions" routerLink="/transactions/{{ transaction.id }}">
|
||||||
<div matLine class="list-row-one">
|
<div matLine class="list-row-one">
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { ExpensesComponent } from './transactions.component';
|
import { TransactionsComponent } from './transactions.component';
|
||||||
|
|
||||||
describe('ExpensesComponent', () => {
|
describe('ExpensesComponent', () => {
|
||||||
let component: ExpensesComponent;
|
let component: TransactionsComponent;
|
||||||
let fixture: ComponentFixture<ExpensesComponent>;
|
let fixture: ComponentFixture<TransactionsComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ ExpensesComponent ]
|
declarations: [ TransactionsComponent ]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(ExpensesComponent);
|
fixture = TestBed.createComponent(TransactionsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
||||||
import { Transaction } from '../transaction';
|
import { Transaction } from '../transaction';
|
||||||
import { TransactionType } from '../transaction.type';
|
import { TransactionType } from '../transaction.type';
|
||||||
import { TransactionService } from '../transaction.service';
|
import { TransactionService } from '../transaction.service';
|
||||||
import { Location } from '@angular/common';
|
import { AppComponent } from '../app.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transactions',
|
selector: 'app-transactions',
|
||||||
|
@ -16,21 +16,19 @@ export class TransactionsComponent implements OnInit {
|
||||||
public transactions: Transaction[]
|
public transactions: Transaction[]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private app: AppComponent,
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private location: Location
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getTransactions()
|
this.app.backEnabled = true;
|
||||||
}
|
this.app.title = 'Transactions';
|
||||||
|
this.getTransactions();
|
||||||
goBack(): void {
|
|
||||||
this.location.back()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTransactions(): void {
|
getTransactions(): void {
|
||||||
this.transactionService.getTransactions().subscribe(transactions => {
|
this.transactionService.getTransactions().subscribe(transactions => {
|
||||||
this.transactions = transactions;
|
this.transactions = transactions;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
src/app/user.ts
Normal file
6
src/app/user.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export class User {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
email: string;
|
||||||
|
}
|
0
src/app/user/user.component.css
Normal file
0
src/app/user/user.component.css
Normal file
3
src/app/user/user.component.html
Normal file
3
src/app/user/user.component.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<p>
|
||||||
|
user works!
|
||||||
|
</p>
|
25
src/app/user/user.component.spec.ts
Normal file
25
src/app/user/user.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserComponent } from './user.component';
|
||||||
|
|
||||||
|
describe('UserComponent', () => {
|
||||||
|
let component: UserComponent;
|
||||||
|
let fixture: ComponentFixture<UserComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ UserComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UserComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
15
src/app/user/user.component.ts
Normal file
15
src/app/user/user.component.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user',
|
||||||
|
templateUrl: './user.component.html',
|
||||||
|
styleUrls: ['./user.component.css']
|
||||||
|
})
|
||||||
|
export class UserComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
html, body {
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
background: #333333;
|
background: #333333;
|
||||||
font-family: Roboto, "Helvetica Neue", sans-serif;
|
font-family: Roboto, "Helvetica Neue", sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -11,6 +13,10 @@ p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.mat-button[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
a.mat-fab {
|
a.mat-fab {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 2em;
|
right: 2em;
|
||||||
|
@ -26,13 +32,15 @@ mat-toolbar {
|
||||||
box-shadow: 0px 3px 3px 1px #212121;
|
box-shadow: 0px 3px 3px 1px #212121;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-toolbar.mat-toolbar-row, mat-toolbar.mat-toolbar-single-row {
|
mat-toolbar.mat-toolbar-row,
|
||||||
|
mat-toolbar.mat-toolbar-single-row {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-toolbar span {
|
mat-toolbar span {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 33%;;
|
width: 33%;
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
mat-toolbar span:nth-child(1) {
|
mat-toolbar span:nth-child(1) {
|
||||||
|
@ -70,11 +78,40 @@ mat-toolbar .action-item a {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mat-sidenav-container.mat-drawer-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-sidenav-container .mat-drawer-backdrop.mat-drawer-shown {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.income {
|
.income {
|
||||||
color: #81C784;
|
color: #81C784;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expense {
|
.expense {
|
||||||
color: #E57373;
|
color: #E57373;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forms
|
||||||
|
*/
|
||||||
|
|
||||||
|
.form {
|
||||||
|
padding: 1em;
|
||||||
|
color: #F1F1F1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form .mat-form-field,
|
||||||
|
.form .button {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form mat-radio-button {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
Loading…
Reference in a new issue