Compare commits

..

7 Commits

Author SHA1 Message Date
Reboooooorn
8d2ba797f9
BETTEZON-102 (#60)
* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
2021-05-20 10:55:19 +02:00
Reboooooorn
099a494db5
BETTERZON-106 (#59)
* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

* wip: new component created and added to the app.module.ts. Added a minimal grid layout.

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
2021-05-20 10:49:02 +02:00
Reboooooorn
854cb454e6
BETTERZON-108 (#58)
* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

* wip: new component created and added to the app.module.ts. Added a minimal grid layout.

* wip: all components were wrapped now. Grid structure has been applied to the main wrapper-class "container".

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
2021-05-20 10:40:52 +02:00
Reboooooorn
e4565f7435
BETTERZON-109 (#57)
* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

* wip: component rewritten, simple grid applied.

* wip: new component created and added to the app.module.ts. Added a minimal grid layout.

* wip: all components were wrapped now. Grid structure has been applied to the main wrapper-class "container".

* wip: component created and added to the app.module.ts

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
2021-05-20 10:36:52 +02:00
Reboooooorn
fc9c7f63cf
BETTERZON-78 (#39)
* BETTERZON-31, dependencies.

* BETTERZON-31: Fixing dependencies

* BETTERZON-31,
BETTERZON-50

info popover and footer had been changed.

* BETTERZON-74

simple top-bar has been created.

* WIP: creating footer using grid.

* BETTERZON-78 adding bottom bar and top bar

* Adding cookieconsent as dependency again since it was removed by a merge

* Adding cookieconsent as dependency again since it was removed by a merge

* Apply suggestions from code review

Switching from single to double quotes

* BETTERZON-78 - grid added, structured as in Adobe XD mockup

Co-authored-by: Patrick Müller <patrick@mueller-patrick.tech>
Co-authored-by: Patrick <50352812+Mueller-Patrick@users.noreply.github.com>
2021-05-20 10:26:10 +02:00
Patrick
e7543e6430
BETTERZON-107: Refactoring code with Proxy as design pattern (#49) 2021-05-20 10:23:41 +02:00
Patrick
ead1f10b25
BETTERZON-110: Refactoring, reformatting and commenting api service (#56) 2021-05-20 10:20:50 +02:00
36 changed files with 17380 additions and 599 deletions

View File

@ -4,7 +4,24 @@ export interface Price {
vendor_id: number; vendor_id: number;
price_in_cents: number; price_in_cents: number;
timestamp: Date; timestamp: Date;
// Only for deals }
amazonDifference?: number;
amazonDifferencePercent?: number; export class Deal implements Price {
price_id: number;
product_id: number;
vendor_id: number;
price_in_cents: number;
timestamp: Date;
amazonDifference: number;
amazonDifferencePercent: number;
constructor(price_id: number, product_id: number, vendor_id: number, price_in_cents: number, timestamp: Date, amazonDifference: number, amazonDifferencePercent: number) {
this.price_id = price_id;
this.product_id = product_id;
this.vendor_id = vendor_id;
this.price_in_cents = price_in_cents;
this.timestamp = timestamp;
this.amazonDifference = amazonDifference;
this.amazonDifferencePercent = amazonDifferencePercent;
}
} }

View File

@ -15,7 +15,7 @@ const pool = mariadb.createPool({
* Data Model Interfaces * Data Model Interfaces
*/ */
import {Price} from './price.interface'; import {Deal, Price} from './price.interface';
import {Prices} from './prices.interface'; import {Prices} from './prices.interface';
@ -255,7 +255,7 @@ export const getBestDeals = async (amount: number): Promise<Prices> => {
} }
// Iterate over all prices to find the products with the biggest difference between amazon and other vendor // Iterate over all prices to find the products with the biggest difference between amazon and other vendor
let deals: Price[] = []; let deals: Deal[] = [];
Object.keys(allPrices).forEach(productId => { Object.keys(allPrices).forEach(productId => {
if (allPrices[parseInt(productId)]) { if (allPrices[parseInt(productId)]) {
@ -287,7 +287,7 @@ export const getBestDeals = async (amount: number): Promise<Prices> => {
// Push only deals were the amazon price is actually higher // Push only deals were the amazon price is actually higher
if (deal.amazonDifferencePercent > 0) { if (deal.amazonDifferencePercent > 0) {
deals.push(deal as Price); deals.push(deal as Deal);
} }
} }
}); });

View File

@ -9,6 +9,5 @@
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Python 3.9 (venv) interpreter library" level="application" />
</component> </component>
</module> </module>

View File

@ -24,6 +24,9 @@
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
{
"input": "src/themes.scss"
},
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.css", "src/styles.css",
"./node_modules/cookieconsent/build/cookieconsent.min.css" "./node_modules/cookieconsent/build/cookieconsent.min.css"
@ -88,13 +91,18 @@
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"codeCoverage": true, "codeCoverage": true,
"codeCoverageExclude": ["src/app/mocks/mock.service.ts"], "codeCoverageExclude": [
"src/app/mocks/mock.service.ts"
],
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
{
"input": "src/themes.scss"
},
"src/styles.css", "src/styles.css",
"./node_modules/cookieconsent/build/cookieconsent.min.css" "./node_modules/cookieconsent/build/cookieconsent.min.css"
], ],

17126
Frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,15 +18,19 @@
"@angular/compiler": "^10.2.3", "@angular/compiler": "^10.2.3",
"@angular/core": "^10.2.3", "@angular/core": "^10.2.3",
"@angular/forms": "^10.2.3", "@angular/forms": "^10.2.3",
"@angular/localize": "^10.2.3",
"@angular/material": "~10.2.7", "@angular/material": "~10.2.7",
"@angular/platform-browser": "^10.2.3", "@angular/platform-browser": "^10.2.3",
"@angular/platform-browser-dynamic": "^10.2.3", "@angular/platform-browser-dynamic": "^10.2.3",
"@angular/router": "^10.2.3", "@angular/router": "^10.2.3",
"@ng-bootstrap/ng-bootstrap": "^8.0.4",
"apexcharts": "^3.22.3", "apexcharts": "^3.22.3",
"bootstrap": "^4.5.0",
"cookieconsent": "^3.1.1", "cookieconsent": "^3.1.1",
"karma-firefox-launcher": "^2.1.0", "karma-firefox-launcher": "^2.1.0",
"ng": "0.0.0", "ng": "0.0.0",
"ng-apexcharts": "^1.5.6", "ng-apexcharts": "^1.5.6",
"ngx-bootstrap": "^6.2.0",
"ngx-cookieconsent": "^2.2.3", "ngx-cookieconsent": "^2.2.3",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"tslib": "^2.0.3", "tslib": "^2.0.3",
@ -42,7 +46,7 @@
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0", "jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0", "jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0", "karma": "^6.3.2",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2", "karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",

View File

@ -0,0 +1 @@

View File

@ -1 +1,13 @@
<div class="container">
<div class="header">
<app-top-bar></app-top-bar>
</div>
<div class="page-content">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div>
<div class="footer">
<app-bottom-bar></app-bottom-bar>
</div>
</div>

View File

@ -20,6 +20,18 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {ImprintComponent} from './pages/imprint/imprint.component'; import {ImprintComponent} from './pages/imprint/imprint.component';
import {PrivacyComponent} from './pages/privacy/privacy.component'; import {PrivacyComponent} from './pages/privacy/privacy.component';
import {NgcCookieConsentModule, NgcCookieConsentConfig} from 'ngx-cookieconsent'; import {NgcCookieConsentModule, NgcCookieConsentConfig} from 'ngx-cookieconsent';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {TopBarComponent} from './components/top-bar/top-bar.component';
import {RouterModule} from '@angular/router';
import {MatButtonModule} from "@angular/material/button";
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatIconModule} from '@angular/material/icon';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatListModule} from "@angular/material/list";
import {BottomBarComponent} from './components/bottom-bar/bottom-bar.component';
import { HotDealsWidgetComponent } from './components/hot-deals-widget/hot-deals-widget.component';
import { SliderForProductsComponent } from './components/slider-for-products/slider-for-products.component';
// For cookie popup // For cookie popup
const cookieConfig: NgcCookieConsentConfig = { const cookieConfig: NgcCookieConsentConfig = {
@ -73,7 +85,11 @@ const cookieConfig: NgcCookieConsentConfig = {
NewestPricesListComponent, NewestPricesListComponent,
PageNotFoundPageComponent, PageNotFoundPageComponent,
ImprintComponent, ImprintComponent,
PrivacyComponent PrivacyComponent,
TopBarComponent,
BottomBarComponent,
HotDealsWidgetComponent,
SliderForProductsComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -83,7 +99,17 @@ const cookieConfig: NgcCookieConsentConfig = {
FormsModule, FormsModule,
MatMenuModule, MatMenuModule,
BrowserAnimationsModule, BrowserAnimationsModule,
NgcCookieConsentModule.forRoot(cookieConfig) NgcCookieConsentModule.forRoot(cookieConfig),
MatSlideToggleModule,
MatButtonModule,
MatToolbarModule,
MatSidenavModule,
MatListModule,
MatButtonModule,
MatIconModule,
RouterModule.forRoot([
{path: '', component: LandingpageComponent},
]),
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -0,0 +1,63 @@
.bottom-bar-wrapper {
display: grid;
grid-template-columns: 546px 546px 546px;
grid-template-rows: 70px 70px 70px;
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.folge-uns-item {
grid-column: 2; grid-row: 1;
justify-self: center;
}
.link-items {
grid-column: 2; grid-row: 2;
justify-self: center;
}
.footer-links li {
display: inline;
margin-right: 60px;
}
#footer-line {
grid-area: 3/1/3/4;
width: 100%;
background-color: #000000;
height: 2px;
}
.bottom-logo {
grid-column: 1; grid-row: 3;
}
.bottom-info {
grid-column: 3; grid-row: 3;
justify-self: right;
}
#folge {
font-size: 46px;
font-weight: bold;
color: #E53167;
margin-right: 10px;
}
#uns {
font-size: 32px;
font-weight: bold;
color: #000000;
}
#better {
font-size: 28px;
font-weight: bold;
color: #3480E3;
}
#zon {
font-size: 28px;
font-weight: bold;
color: #E53167;
}

View File

@ -0,0 +1,26 @@
<div class="bottom-bar-wrapper">
<div class="folge-uns-item">
<p><span id="folge">FOLGE</span><span id="uns">UNS</span></p>
</div>
<div class="link-items">
<ul style="list-style-type:none" class="footer-links">
<li><a href="https://github.com/Mueller-Patrick/Betterzon">GiT</a></li>
<li><a href="https://blog.betterzon.xyz/">BLOG</a></li>
<li><a href="https://github.com/Mueller-Patrick/Betterzon/wiki">Wiki</a></li>
</ul>
</div>
<div id="footer-line">
</div>
<div class="bottom-logo">
<p><span id="better">BETTER</span><span id="zon">ZON</span></p>
</div>
<div class="bottom-info">
<ul style="list-style-type:none" class="footer-links">
<li><a>DATENSCHUTZERKLÄRUNG</a></li>
<li><a>IMPRESSUM</a></li>
</ul>
</div>
</div>

View File

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

View File

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

View File

@ -3,7 +3,7 @@
left: 0; left: 0;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
background-color: dimgrey; background-color: #1976d2;
color: white; color: white;
} }
@ -30,5 +30,3 @@
text-decoration: none; text-decoration: none;
} }

View File

@ -48,3 +48,8 @@
padding: 10px; padding: 10px;
color: #fff; color: #fff;
} }
.slider {
position: relative;
margin: auto;
}

View File

@ -7,6 +7,9 @@
<div class="searchBox"> <div class="searchBox">
<input *ngIf="showSearch===true" type="text" [(ngModel)]="searchInput" placeholder="Search" (keyup.enter)="startedSearch()"> <input *ngIf="showSearch===true" type="text" [(ngModel)]="searchInput" placeholder="Search" (keyup.enter)="startedSearch()">
</div> </div>
<div class="slider">
<mat-slide-toggle color="primary">dark me</mat-slide-toggle>
</div>
<div class="profileIcon"> <div class="profileIcon">
<button mat-button [matMenuTriggerFor]="menu">Menu</button> <button mat-button [matMenuTriggerFor]="menu">Menu</button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">

View File

@ -0,0 +1,72 @@
.hot-deal-widget-wrapper{
width: 1640px;
height: 820px;
background-color: #f8f9fa;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 0px;
grid-row-gap: 0px;
align-items: center;
}
.product-description {
/*background-color: #3480E3;*/
height: 100%;
display: grid;
grid-template-columns: 15% 16px 15% 16px 15% 16px 15% 16px 15% 16px 15% 8px;
grid-template-rows: repeat(5, 1fr);
}
.product-image {
}
#hot-deals{
/*background-color: #E53167;*/
justify-self: center;
align-self: center;
grid-column: 3/10;
grid-row: 1/2;
}
#product-name {
justify-self: center;
align-self: center;
grid-column: 3/10;
grid-row: 2/3;
/*background-color: #E53167;*/
}
#product-name > p {
font-size: 65px;
}
#sales {
justify-self: center;
align-self: center;
grid-column: 3/10;
grid-row: 3/4;
/*background-color: #E53167;*/
}
#futher-informations {
justify-self: center;
align-self: center;
grid-column: 3/10;
grid-row: 4/5;
/*background-color: #E53167;*/
}
#points {
justify-self: center;
align-self: start;
grid-column: 3/10;
grid-row: 5/6;
/*background-color: #E53167;*/
}
.product-image {
display: grid;
justify-content: center;
}

View File

@ -0,0 +1,23 @@
<div class="hot-deal-widget-wrapper">
<div class="product-description">
<div id="hot-deals">
<h1>HOT DEALS</h1>
</div>
<div id="product-name">
<h1>Neues Apple iPhone 12 Pro <br> (512 GB) - Graphit</h1>
</div>
<div id="sales">
SPARE BIS ZU 7%!
</div>
<div id="futher-informations">
Weitere Informationen
</div>
<div id="points">
points
</div>
</div>
<div class="product-image">
<img src="assets/images/iphone-12-pro-silver-hero.png" height="771">
</div>
</div>

View File

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

View File

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

View File

@ -0,0 +1 @@
<p>slider-for-products works!</p>

View File

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

View File

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

View File

@ -0,0 +1,54 @@
.top-bar-wrapper {
display: grid;
grid-template-columns: 200px 360px 820px 20px 250px;
grid-template-rows: 40px;
grid-column-gap: 0px;
grid-row-gap: 0px;
align-items: center;
}
.top-logo {
grid-area: 1/1;
}
#better {
font-size: 28px;
font-weight: bold;
color: #3480E3;
}
#zon {
font-size: 28px;
font-weight: bold;
color: #E53167;
}
.search-button {
/*background-color: #E53167;*/
}
.sign-up {
/*background-color: #E53167;*/
margin-left: 50px;
margin-right: 25px;
}
.login {
margin-right: 25px;
}
#signin {
border-radius: 25px;
background-color: #E53167;
}
._links > a {
/*background-color: #E53167;*/
margin-left: 10px;
margin-right: 10px;
}
._signing_links > a {
/*background-color: #E53167;*/
margin-left: 50px;
}

View File

@ -0,0 +1,26 @@
<div class="top-bar-wrapper">
<div class="<top-logo>">
<a><span id="better">BETTER</span><span id="zon">ZON</span></a>
</div>
<div class="links">
<nav class="_links">
<a>KONTAKTIERE UNS</a>
<a>KUNDEN</a>
<a>FAQ</a>
</nav>
</div>
<div class="footer_space">
</div>
<div class="search-button">
<a>
<img src="assets/images/search_black_24dp.svg" alt="Sarch button">
</a>
</div>
<div class="links">
<nav class="_signing_links">
<a>SIGN UP</a>
<a><span id="signin">SIGN IN</span></a>
</nav>
</div>
</div>

View File

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

View File

@ -0,0 +1,17 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-top-bar',
templateUrl: './top-bar.component.html',
styleUrls: ['./top-bar.component.css']
})
export class TopBarComponent implements OnInit {
sidenav: any;
constructor() { }
ngOnInit(): void {
}
}

View File

@ -1,5 +1,6 @@
<app-header [showSearch]="false"></app-header> <app-hot-deals-widget></app-hot-deals-widget>
<div id="mainComponents">
<!--<div id="mainComponents">
<div id="searchContainer"> <div id="searchContainer">
<input type="text" [(ngModel)]="searchInput" placeholder="Search" (keyup.enter)="startedSearch()"> <input type="text" [(ngModel)]="searchInput" placeholder="Search" (keyup.enter)="startedSearch()">
</div> </div>
@ -15,4 +16,4 @@
</div> </div>
</div> </div>
</div> </div>
<app-footer></app-footer> -->

View File

@ -1,4 +1,3 @@
<app-header [showSearch]="true"></app-header>
<div id="mainComponents"> <div id="mainComponents">
<app-product-details [productId]="productId"></app-product-details> <app-product-details [productId]="productId"></app-product-details>
<app-newest-prices-list [productId]="productId"></app-newest-prices-list> <app-newest-prices-list [productId]="productId"></app-newest-prices-list>

View File

@ -1,6 +1,6 @@
<app-header [showSearch]="true"></app-header>
<div id="mainComponents"> <div id="mainComponents">
<app-product-list numberOfProducts="20" [showProductPicture]="true" searchQuery="{{searchTerm}}" <app-product-list numberOfProducts="20" [showProductPicture]="true" searchQuery="{{searchTerm}}"
type="search"></app-product-list> type="search"></app-product-list>
</div> </div>
<app-footer></app-footer> <app-footer></app-footer>

View File

@ -18,116 +18,191 @@ export class ApiService {
) { ) {
} }
/* ____ __ __
/ __ \_________ ____/ /_ _______/ /______
/ /_/ / ___/ __ \/ __ / / / / ___/ __/ ___/
/ ____/ / / /_/ / /_/ / /_/ / /__/ /_(__ )
/_/ /_/ \____/\__,_/\__,_/\___/\__/____/
*/
/**
* Gets the specified product from the API
* @param id The id of the product to get
* @return Observable<Product> An observable containing a single product
*/
getProduct(id): Observable<Product> { getProduct(id): Observable<Product> {
try { try {
const prod = this.http.get<Product>((this.apiUrl + '/products/' + id)); return this.http.get<Product>((this.apiUrl + '/products/' + id));
return prod;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Gets a list of products that match the given search term
* @param query The search term to match
* @return Observable<Product[]> An observable list of products
*/
getProductsByQuery(query): Observable<Product[]> { getProductsByQuery(query): Observable<Product[]> {
try { try {
const prods = this.http.get<Product[]>((this.apiUrl + '/products/search/' + query)); return this.http.get<Product[]>((this.apiUrl + '/products/search/' + query));
return prods;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Gets a list of all products
* @return Observable<Product[]> An observable list of products
*/
getProducts(): Observable<Product[]> { getProducts(): Observable<Product[]> {
try { try {
const prods = this.http.get<Product[]>((this.apiUrl + '/products')); return this.http.get<Product[]>((this.apiUrl + '/products'));
return prods;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/* ____ _
/ __ \_____(_)_______ _____
/ /_/ / ___/ / ___/ _ \/ ___/
/ ____/ / / / /__/ __(__ )
/_/ /_/ /_/\___/\___/____/
*/
/**
* Gets a list of all prices
* @return Observable<Price[]> An observable list of prices
*/
getPrices(): Observable<Price[]> { getPrices(): Observable<Price[]> {
try { try {
const prices = this.http.get<Price[]>((this.apiUrl + '/prices')); return this.http.get<Price[]>((this.apiUrl + '/prices'));
return prices;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Gets the lowest prices of every vendor for the given product
* @param productId The product id of the product to fetch the prices for
* @return Observable<Price[]> An observable list of prices
*/
getLowestPrices(productId): Observable<Price[]> { getLowestPrices(productId): Observable<Price[]> {
try { try {
let params = new HttpParams(); let params = new HttpParams();
params = params.append('product', productId); params = params.append('product', productId);
params = params.append('type', 'lowest'); params = params.append('type', 'lowest');
const prices = this.http.get<Price[]>((this.apiUrl + '/prices'), {params}); return this.http.get<Price[]>((this.apiUrl + '/prices'), {params});
return prices;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Gets the latest amazon price for the given product
* @param productId The product id of the product to get the price for
* @return Observable<Price> An observable containing a single price
*/
getAmazonPrice(productId): Observable<Price> { getAmazonPrice(productId): Observable<Price> {
try { try {
let params = new HttpParams(); let params = new HttpParams();
params = params.append('product', productId); params = params.append('product', productId);
params = params.append('vendor', '1'); params = params.append('vendor', '1');
params = params.append('type', 'newest'); params = params.append('type', 'newest');
const price = this.http.get<Price>((this.apiUrl + '/prices'), {params}); return this.http.get<Price>((this.apiUrl + '/prices'), {params});
return price;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Gets the newest prices of every vendor for the given product
* @param productId The product id of the product to fetch the prices for
* @return Observable<Price[]> An observable list of prices
*/
getCurrentPricePerVendor(productId): Observable<Price[]> { getCurrentPricePerVendor(productId): Observable<Price[]> {
try { try {
let params = new HttpParams(); let params = new HttpParams();
params = params.append('product', productId); params = params.append('product', productId);
params = params.append('type', 'newest'); params = params.append('type', 'newest');
const prices = this.http.get<Price[]>((this.apiUrl + '/prices'), {params}); return this.http.get<Price[]>((this.apiUrl + '/prices'), {params});
return prices;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/* _ __ __
| | / /__ ____ ____/ /___ __________
| | / / _ \/ __ \/ __ / __ \/ ___/ ___/
| |/ / __/ / / / /_/ / /_/ / / (__ )
|___/\___/_/ /_/\__,_/\____/_/ /____/
*/
/**
* Gets a list of all vendors
* @return Observable<Vendor[]> An observable list of vendors
*/
getVendors(): Observable<Vendor[]> { getVendors(): Observable<Vendor[]> {
try { try {
const vendors = this.http.get<Vendor[]>((this.apiUrl + '/vendors')); return this.http.get<Vendor[]>((this.apiUrl + '/vendors'));
return vendors;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/* ____ _ ___ __
/ __ \_____(_)_______ / | / /___ __________ ___ _____
/ /_/ / ___/ / ___/ _ \ / /| | / / __ `/ ___/ __ `__ \/ ___/
/ ____/ / / / /__/ __/ / ___ |/ / /_/ / / / / / / / (__ )
/_/ /_/ /_/\___/\___/ /_/ |_/_/\__,_/_/ /_/ /_/ /_/____/
*/
/**
* Gets a list of all price alarms
* @return Observable<PriceAlarm[]> An observable list of price alarms
*/
getPriceAlarms(): Observable<PriceAlarm[]> { getPriceAlarms(): Observable<PriceAlarm[]> {
try { try {
const alarms = this.http.get<PriceAlarm[]>((this.apiUrl + '/pricealarms')); return this.http.get<PriceAlarm[]>((this.apiUrl + '/pricealarms'));
return alarms;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Creates a new price alarm
* @param productId The product id of the product to create the alarm for
* @param definedPrice The defined target price
* @return Observable<any> The observable response of the api
*/
createPriceAlarms(productId: number, definedPrice: number): Observable<any> { createPriceAlarms(productId: number, definedPrice: number): Observable<any> {
try { try {
const res = this.http.post((this.apiUrl + '/pricealarms'), JSON.stringify({ return this.http.post((this.apiUrl + '/pricealarms'), JSON.stringify({
product_id: productId, product_id: productId,
defined_price: definedPrice defined_price: definedPrice
})); }));
return res;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }
} }
/**
* Updates the given price alarm
* @param alarmId The alarm id of the alarm to update
* @param definedPrice The defined target price
* @return Observable<any> The observable response of the api
*/
updatePriceAlarms(alarmId: number, definedPrice: number): Observable<any> { updatePriceAlarms(alarmId: number, definedPrice: number): Observable<any> {
try { try {
const res = this.http.put((this.apiUrl + '/pricealarms'), JSON.stringify({ return this.http.put((this.apiUrl + '/pricealarms'), JSON.stringify({
alarm_id: alarmId, alarm_id: alarmId,
defined_price: definedPrice defined_price: definedPrice
})); }));
return res;
} catch (exception) { } catch (exception) {
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`); process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="18px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@ -1,9 +1,169 @@
/* You can add global styles to this file, and also import other style files */ /* general settings */
body {
margin: 0; * {
font-family: sans-serif; box-sizing: border-box;
margin-bottom: 10em;
} }
html, body { height: 100%; } html, body {
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } height: 100%;
margin: 0;
background-color: #FFFFFF;
}
/* Text */
h1 {
font-size: 32px;
}
h2 {
font-size: 20px;
}
h1, h2 {
font-weight: lighter;
}
h3 {
font-size: 18px;
}
p {
font-size: 14px;
}
/* Hyperlink */
a {
cursor: pointer;
color: #000000;
text-decoration: none;
}
a:hover {
opacity: 0.8;
color: #3480E3;
}
a, p{
font-size: 16px;
}
/* links */
/* Input */
input {
font-size: 14px;
border-radius: 2px;
padding: 8px;
margin-bottom: 16px;
border: 1px solid #BDBDBD;
}
label {
font-size: 12px;
font-weight: bold;
margin-bottom: 4px;
display: block;
text-transform: uppercase;
}
/* Button */
.button, button {
display: inline-flex;
align-items: center;
padding: 8px 16px;
border-radius: 2px;
font-size: 14px;
cursor: pointer;
background-color: #1976d2;
color: white;
border: none;
}
.button:hover, button:hover {
opacity: 0.8;
font-weight: normal;
}
.button:disabled, button:disabled {
opacity: 0.5;
cursor: auto;
}
/* Fancy Button */
.fancy-button {
background-color: white;
color: #1976d2;
}
.fancy-button i.material-icons {
color: #1976d2;
padding-right: 4px;
}
/* Main Container */
.container {
position: fixed;
left: 50%;
margin-left: -820px;
width: 1640px;
height: auto;
display: grid;
grid-template-rows: 70px auto 210px;
}
.header {
grid-row: 1/2;
}
.page-content {
grid-row: 2/3;
}
.footer {
grid-row: 3/4;
}
/* End of Main Container */
/* Top Bar */
app-top-bar {
width: 1640px;
height: 70px;
background-color: #ffffff;
padding: 16px;
position: fixed;
left: 50%;
top:0;
margin-left: -820px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
/*box-shadow: 0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12)*/
}
app-top-bar h1 {
color: white;
margin: 0;
}
/* Bottom Bar */
app-bottom-bar{
background-color: #F8F8F8;
width: 1640px;
height: 210px;
position: fixed;
margin-top: 90px;
bottom: 0;
flex-direction: row;
display: flex;
justify-content: space-between;
}

15
Frontend/src/themes.scss Normal file
View File

@ -0,0 +1,15 @@
@import '~@angular/material/theming';
@include mat-core();
$themes_primary: mat-palette($mat-green);
$themes_accent: mat-palette($mat-pink, A200, A100, A400);
$themes_theme: mat-light-theme((
color: (
primary: $themes_primary,
accent: $themes_accent,
)
));
@include angular-material-theme($themes_theme);