├── src ├── assets │ ├── .gitkeep │ └── img │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ ├── slider1.png │ │ ├── slider2.png │ │ └── slider3.png ├── app │ ├── app.component.css │ ├── admin │ │ ├── auth │ │ │ ├── auth.component.css │ │ │ ├── auth.component.spec.ts │ │ │ ├── auth.component.html │ │ │ └── auth.component.ts │ │ ├── orders │ │ │ └── order-list │ │ │ │ ├── order-list.component.css │ │ │ │ ├── order-list.component.html │ │ │ │ ├── order-list.component.ts │ │ │ │ └── order-list.component.spec.ts │ │ ├── products │ │ │ ├── product-form │ │ │ │ ├── product-form.component.css │ │ │ │ ├── product-form.component.spec.ts │ │ │ │ ├── product-form.component.html │ │ │ │ └── product-form.component.ts │ │ │ └── product-list │ │ │ │ ├── product-list.component.css │ │ │ │ ├── product-list.component.ts │ │ │ │ ├── product-list.component.spec.ts │ │ │ │ └── product-list.component.html │ │ ├── categories │ │ │ ├── category-form │ │ │ │ ├── category-form.component.css │ │ │ │ ├── category-form.component.html │ │ │ │ ├── category-form.component.spec.ts │ │ │ │ └── category-form.component.ts │ │ │ └── category-list │ │ │ │ ├── category-list.component.css │ │ │ │ ├── category-list.component.html │ │ │ │ ├── category-list.component.ts │ │ │ │ └── category-list.component.spec.ts │ │ ├── admin.component.ts │ │ ├── auth.guard.ts │ │ ├── admin.component.html │ │ ├── admin.module.ts │ │ └── admin-routing.module.ts │ ├── shop │ │ ├── navbar │ │ │ ├── navbar.component.css │ │ │ ├── navbar.component.ts │ │ │ ├── navbar.component.spec.ts │ │ │ └── navbar.component.html │ │ ├── cart-detail │ │ │ ├── cart-detail.component.css │ │ │ ├── cart-detail.component.ts │ │ │ ├── cart-detail.component.spec.ts │ │ │ └── cart-detail.component.html │ │ ├── cart-summary │ │ │ ├── cart-summary.component.css │ │ │ ├── cart-summary.component.html │ │ │ ├── cart-summary.component.ts │ │ │ └── cart-summary.component.spec.ts │ │ ├── product-list │ │ │ ├── product-list.component.css │ │ │ ├── product-list.component.spec.ts │ │ │ ├── product-list.component.ts │ │ │ └── product-list.component.html │ │ ├── category-list │ │ │ ├── category-list.component.css │ │ │ ├── category-list.component.html │ │ │ ├── category-list.component.spec.ts │ │ │ └── category-list.component.ts │ │ ├── checkout │ │ │ ├── checkout.component.css │ │ │ ├── checkout.component.spec.ts │ │ │ ├── checkout.component.ts │ │ │ └── checkout.component.html │ │ ├── shop.module.ts │ │ ├── shop.component.ts │ │ └── shop.component.html │ ├── model │ │ ├── category.model.ts │ │ ├── product.model.ts │ │ ├── order.repository.ts │ │ ├── auth.service.ts │ │ ├── order.model.ts │ │ ├── model.module.ts │ │ ├── category.repository.ts │ │ ├── cart.model.ts │ │ ├── product.repository.ts │ │ └── rest.service.ts │ ├── app.component.ts │ ├── app.module.ts │ └── app.component.spec.ts ├── favicon.ico ├── main.ts ├── styles.css └── index.html ├── screenshots ├── ss1.png ├── ss2.png ├── ss3.png └── ss4.png ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── tsconfig.app.json ├── tsconfig.spec.json ├── .editorconfig ├── README.md ├── .gitignore ├── tsconfig.json ├── data.js ├── auth-middleware.js ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/auth/auth.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shop/navbar/navbar.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shop/cart-detail/cart-detail.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shop/cart-summary/cart-summary.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shop/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/orders/order-list/order-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shop/category-list/category-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/products/product-form/product-form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/products/product-list/product-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/categories/category-form/category-form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/categories/category-list/category-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/admin/orders/order-list/order-list.component.html: -------------------------------------------------------------------------------- 1 |

order-list works!

2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /screenshots/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/screenshots/ss1.png -------------------------------------------------------------------------------- /screenshots/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/screenshots/ss2.png -------------------------------------------------------------------------------- /screenshots/ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/screenshots/ss3.png -------------------------------------------------------------------------------- /screenshots/ss4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/screenshots/ss4.png -------------------------------------------------------------------------------- /src/assets/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/1.jpg -------------------------------------------------------------------------------- /src/assets/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/2.jpg -------------------------------------------------------------------------------- /src/assets/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/3.jpg -------------------------------------------------------------------------------- /src/assets/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/4.jpg -------------------------------------------------------------------------------- /src/assets/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/5.jpg -------------------------------------------------------------------------------- /src/assets/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/6.jpg -------------------------------------------------------------------------------- /src/assets/img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/7.jpg -------------------------------------------------------------------------------- /src/assets/img/slider1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/slider1.png -------------------------------------------------------------------------------- /src/assets/img/slider2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/slider2.png -------------------------------------------------------------------------------- /src/assets/img/slider3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appleseed619/angular_shopping_site/HEAD/src/assets/img/slider3.png -------------------------------------------------------------------------------- /src/app/model/category.model.ts: -------------------------------------------------------------------------------- 1 | export class Category { 2 | constructor( 3 | public id?: number, 4 | public name?: string 5 | ) { } 6 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /src/app/shop/checkout/checkout.component.css: -------------------------------------------------------------------------------- 1 | .form-control.ng-touched.ng-invalid { 2 | border-left: 5px solid red; 3 | } 4 | 5 | .form-control.ng-dirty.ng-valid { 6 | border-left: 5px solid green; 7 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'root', 5 | template: "", 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /src/app/shop/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'navbar', 5 | templateUrl: './navbar.component.html', 6 | styleUrls: ['./navbar.component.css'] 7 | }) 8 | export class NavbarComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/admin/orders/order-list/order-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-order-list', 5 | templateUrl: './order-list.component.html', 6 | styleUrls: ['./order-list.component.css'] 7 | }) 8 | export class OrderListComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/app/model/product.model.ts: -------------------------------------------------------------------------------- 1 | export class Product { 2 | constructor( 3 | public id?: number, 4 | public name?: string, 5 | public price?: number, 6 | public imageUrl?: string, 7 | public description?: string, 8 | public category?: string 9 | ) { } 10 | } -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | .pt-100 { 3 | padding-top: 100px; 4 | } 5 | 6 | .ml-3 { 7 | margin-left: 1rem; 8 | } 9 | 10 | .ml-2 { 11 | margin-left: 0.7rem; 12 | } 13 | 14 | .m-0 { 15 | margin: 0; 16 | } 17 | 18 | .p-0 { 19 | padding: 0; 20 | } -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/app/shop/cart-summary/cart-summary.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | There are {{cart.itemCount}} items on your cart.
4 | Total price: {{cart.total}} 5 |
6 | You haven't added any products to your cart yet. 7 |
-------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Shop Application 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/shop/cart-detail/cart-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Cart } from 'src/app/model/cart.model'; 3 | 4 | @Component({ 5 | selector: 'cart-detail', 6 | templateUrl: './cart-detail.component.html', 7 | styleUrls: ['./cart-detail.component.css'] 8 | }) 9 | export class CartDetailComponent { 10 | constructor(public cart: Cart) { } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/shop/cart-summary/cart-summary.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Cart } from 'src/app/model/cart.model'; 3 | 4 | @Component({ 5 | selector: 'cart-summary', 6 | templateUrl: './cart-summary.component.html', 7 | styleUrls: ['./cart-summary.component.css'] 8 | }) 9 | export class CartSummaryComponent { 10 | constructor(public cart: Cart) { } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/app/shop/category-list/category-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 9 |
-------------------------------------------------------------------------------- /src/app/admin/admin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { Router } from "@angular/router"; 3 | import { AuthService } from "../model/auth.service"; 4 | 5 | 6 | @Component({ 7 | templateUrl: 'admin.component.html' 8 | }) 9 | export class AdminComponent { 10 | constructor(private authService: AuthService, private router: Router) { } 11 | 12 | logout() { 13 | this.authService.clear(); 14 | this.router.navigateByUrl("/shop"); 15 | } 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Shopping App 2 | 3 | ## Start Project 4 | - `npm install` to install all dependencies 5 | - `npm run json` to start the json-server 6 | - `ng serve` to start the project 7 | ## Used Technologies 8 | - Angular 15 9 | - json-server 10 | - jsonwebtoken 11 | - jwt 12 | - Http Methods 13 | - Bootstrap 5 14 | - Font Awesome 15 | 16 | ## Screenshots 17 | 18 | ![ss1](./screenshots/ss1.png) 19 | ![ss2](./screenshots/ss2.png) 20 | ![ss3](./screenshots/ss3.png) 21 | ![ss4](./screenshots/ss4.png) 22 | -------------------------------------------------------------------------------- /src/app/admin/categories/category-form/category-form.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{ editing ? "Edit": "Create" }} Category 3 |
4 | 5 |
6 |
7 | 8 | 9 |
10 | 13 | 14 |
-------------------------------------------------------------------------------- /src/app/model/order.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Observable } from "rxjs"; 3 | import { Order } from "./order.model"; 4 | import { RestService } from "./rest.service"; 5 | 6 | @Injectable() 7 | export class OrderRepository { 8 | private orders: Order[] = []; 9 | 10 | constructor(private restService: RestService) { } 11 | 12 | getOrders(): Order[] { 13 | return this.orders; 14 | } 15 | 16 | saveOrder(order: Order): Observable { 17 | return this.restService.saveOrder(order); 18 | } 19 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/app/model/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { RestService } from './rest.service'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable() 6 | export class AuthService { 7 | constructor(private restService: RestService) { } 8 | 9 | authenticate(username: string, password: string): Observable { 10 | return this.restService.authentication(username, password); 11 | } 12 | 13 | get authenticated(): boolean { 14 | return this.restService.token != null; 15 | } 16 | 17 | clear() { 18 | this.restService.token = null; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/model/order.model.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Cart } from "./cart.model"; 3 | 4 | @Injectable() 5 | export class Order { 6 | public id: number; 7 | public name: string; 8 | public address: string; 9 | public city: string; 10 | public phone: string; 11 | public email: string; 12 | 13 | public isSent: boolean = false; 14 | 15 | constructor(public cart: Cart) { } 16 | 17 | clearOrder() { 18 | this.id = null; 19 | this.name = this.address = this.city = this.phone = this.email = null; 20 | this.isSent = false; 21 | this.cart.clear(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/admin/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; 3 | import { AuthService } from "../model/auth.service"; 4 | 5 | @Injectable() 6 | export class AuthGuard implements CanActivate { 7 | constructor( 8 | private router: Router, 9 | private authService: AuthService 10 | ) { } 11 | 12 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 13 | if (!this.authService.authenticated) { 14 | this.router.navigateByUrl('/admin/auth'); 15 | return false; 16 | } 17 | return true; 18 | } 19 | } -------------------------------------------------------------------------------- /src/app/model/model.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { HttpClientModule } from "@angular/common/http"; 3 | import { RestService } from "./rest.service"; 4 | import { ProductRepository } from "./product.repository"; 5 | import { CategoryRepository } from "./category.repository"; 6 | import { Cart } from "./cart.model"; 7 | import { OrderRepository } from "./order.repository"; 8 | import { Order } from "./order.model"; 9 | import { AuthService } from "./auth.service"; 10 | 11 | @NgModule({ 12 | imports: [HttpClientModule], 13 | providers: [RestService, ProductRepository, CategoryRepository, Cart, Order, OrderRepository, AuthService] 14 | }) 15 | export class ModelModule { } -------------------------------------------------------------------------------- /src/app/admin/auth/auth.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthComponent } from './auth.component'; 4 | 5 | describe('AuthComponent', () => { 6 | let component: AuthComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AuthComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AuthComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/products/product-list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Product } from 'src/app/model/product.model'; 3 | import { ProductRepository } from 'src/app/model/product.repository'; 4 | 5 | @Component({ 6 | selector: 'app-product-list', 7 | templateUrl: './product-list.component.html', 8 | styleUrls: ['./product-list.component.css'] 9 | }) 10 | export class ProductListComponent { 11 | constructor(private productRepository: ProductRepository) { } 12 | 13 | getProducts(): Product[] { 14 | return this.productRepository.getProducts(); 15 | } 16 | 17 | deleteProduct(product: Product){ 18 | this.productRepository.deleteProduct(product); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/shop/navbar/navbar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NavbarComponent } from './navbar.component'; 4 | 5 | describe('NavbarComponent', () => { 6 | let component: NavbarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ NavbarComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(NavbarComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/app/shop/checkout/checkout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CheckoutComponent } from './checkout.component'; 4 | 5 | describe('CheckoutComponent', () => { 6 | let component: CheckoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CheckoutComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CheckoutComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/orders/order-list/order-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { OrderListComponent } from './order-list.component'; 4 | 5 | describe('OrderListComponent', () => { 6 | let component: OrderListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ OrderListComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(OrderListComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/shop/cart-detail/cart-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CartDetailComponent } from './cart-detail.component'; 4 | 5 | describe('CartDetailComponent', () => { 6 | let component: CartDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CartDetailComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CartDetailComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/shop/cart-summary/cart-summary.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CartSummaryComponent } from './cart-summary.component'; 4 | 5 | describe('CartSummaryComponent', () => { 6 | let component: CartSummaryComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CartSummaryComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CartSummaryComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/shop/product-list/product-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductListComponent } from './product-list.component'; 4 | 5 | describe('ProductListComponent', () => { 6 | let component: ProductListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProductListComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ProductListComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/categories/category-list/category-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 |
name
{{p.name}} 13 | 14 | 15 |
19 | -------------------------------------------------------------------------------- /src/app/admin/products/product-form/product-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductFormComponent } from './product-form.component'; 4 | 5 | describe('ProductFormComponent', () => { 6 | let component: ProductFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProductFormComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ProductFormComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/products/product-list/product-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ProductListComponent } from './product-list.component'; 4 | 5 | describe('ProductListComponent', () => { 6 | let component: ProductListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ProductListComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ProductListComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/shop/category-list/category-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CategoryListComponent } from './category-list.component'; 4 | 5 | describe('CategoryListComponent', () => { 6 | let component: CategoryListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CategoryListComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CategoryListComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/categories/category-list/category-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { CategoryRepository } from 'src/app/model/category.repository'; 3 | import { Category } from 'src/app/model/category.model'; 4 | 5 | @Component({ 6 | selector: 'app-category-list', 7 | templateUrl: './category-list.component.html', 8 | styleUrls: ['./category-list.component.css'] 9 | }) 10 | export class CategoryListComponent implements OnInit { 11 | 12 | constructor(private repository: CategoryRepository) { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | getCategories(): Category[] { 18 | return this.repository.getCategories(); 19 | } 20 | 21 | deleteCategory(category: Category) { 22 | this.repository.deleteCategory(category); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/app/admin/categories/category-form/category-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CategoryFormComponent } from './category-form.component'; 4 | 5 | describe('CategoryFormComponent', () => { 6 | let component: CategoryFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CategoryFormComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CategoryFormComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/categories/category-list/category-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CategoryListComponent } from './category-list.component'; 4 | 5 | describe('CategoryListComponent', () => { 6 | let component: CategoryListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CategoryListComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CategoryListComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/admin/auth/auth.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{errorMessage}} 3 |
4 | 5 |
6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
-------------------------------------------------------------------------------- /src/app/admin/products/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 |
nameprice
{{p.name}}{{p.price}} EUR 16 | 17 | 18 |
22 | -------------------------------------------------------------------------------- /src/app/shop/category-list/category-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | import { Category } from 'src/app/model/category.model'; 3 | import { CategoryRepository } from 'src/app/model/category.repository'; 4 | 5 | @Component({ 6 | selector: 'category-list', 7 | templateUrl: './category-list.component.html', 8 | styleUrls: ['./category-list.component.css'] 9 | }) 10 | 11 | export class CategoryListComponent { 12 | public selectedCategory: Category = null; 13 | @Output() category = new EventEmitter(); 14 | 15 | constructor( 16 | private categoryRepository: CategoryRepository 17 | ) { } 18 | 19 | 20 | get categories(): Category[] { 21 | return this.categoryRepository.getCategories(); 22 | } 23 | 24 | 25 | changeCategory(newCategory?: Category) { 26 | this.selectedCategory = newCategory; 27 | this.category.emit(newCategory); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/shop/checkout/checkout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Order } from 'src/app/model/order.model'; 4 | import { OrderRepository } from 'src/app/model/order.repository'; 5 | 6 | 7 | @Component({ 8 | selector: 'checkout', 9 | templateUrl: './checkout.component.html', 10 | styleUrls: ['./checkout.component.css'] 11 | }) 12 | export class CheckoutComponent { 13 | orderSent: boolean = false; 14 | submitted: boolean = false; 15 | 16 | constructor(public order: Order, private orderRepository: OrderRepository) { } 17 | 18 | submitOrder(form: NgForm) { 19 | this.submitted = true; 20 | if (form.valid) { 21 | this.orderRepository.saveOrder(this.order) 22 | .subscribe(order => { 23 | this.order.clearOrder(); 24 | this.orderSent = true; 25 | this.submitted = false; 26 | }); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/shop/product-list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Cart } from 'src/app/model/cart.model'; 4 | import { Product } from 'src/app/model/product.model'; 5 | 6 | @Component({ 7 | selector: 'product-list', 8 | templateUrl: './product-list.component.html', 9 | styleUrls: ['./product-list.component.css'] 10 | }) 11 | export class ProductListComponent { 12 | 13 | @Input() products: Product[] = []; 14 | selectedProduct: Product = null; 15 | 16 | constructor( 17 | private cart: Cart, 18 | private router: Router 19 | ) { } 20 | 21 | 22 | addProductToCart(product: Product) { 23 | this.cart.addItem(product); 24 | this.router.navigateByUrl('/cart'); 25 | } 26 | 27 | displayDetails(product: Product) { 28 | this.selectedProduct = product; 29 | } 30 | 31 | hideDetails() { 32 | this.selectedProduct = null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/admin/admin.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Admin Panel 5 |
6 |
7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
-------------------------------------------------------------------------------- /src/app/admin/auth/auth.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { AuthService } from 'src/app/model/auth.service'; 5 | 6 | @Component({ 7 | selector: 'app-auth', 8 | templateUrl: './auth.component.html', 9 | styleUrls: ['./auth.component.css'] 10 | }) 11 | export class AuthComponent { 12 | public username: string; 13 | public password: string; 14 | public errorMessage: string; 15 | 16 | constructor( 17 | private router: Router, 18 | private authService: AuthService 19 | ) { } 20 | 21 | login(form: NgForm) { 22 | if (form.valid) { 23 | this.authService.authenticate(this.username, this.password) 24 | .subscribe(response => { 25 | if (response) { 26 | this.router.navigateByUrl('/admin/main'); 27 | } 28 | this.errorMessage = 'Authentication Failed'; 29 | }) 30 | } else { 31 | this.errorMessage = 'Enter complete information'; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "useDefineForClassFields": false, 22 | "strictNullChecks": false, 23 | "lib": [ 24 | "ES2022", 25 | "dom" 26 | ] 27 | }, 28 | "angularCompilerOptions": { 29 | "enableI18nLegacyMessageIdFormat": false, 30 | "strictInjectionParameters": true, 31 | "strictInputAccessModifiers": true, 32 | "strictTemplates": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/admin/products/product-form/product-form.component.html: -------------------------------------------------------------------------------- 1 |
2 | {{editing ? 'Edit' : 'Create'}} Product 3 |
4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 | 25 | 26 |
-------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { CartDetailComponent } from './shop/cart-detail/cart-detail.component'; 7 | import { CheckoutComponent } from './shop/checkout/checkout.component'; 8 | import { ShopComponent } from './shop/shop.component'; 9 | import { ShopModule } from './shop/shop.module'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | ShopModule, 18 | RouterModule.forRoot([ 19 | { path: 'shop', component: ShopComponent }, 20 | { path: 'cart', component: CartDetailComponent }, 21 | { path: 'checkout', component: CheckoutComponent }, 22 | { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }, 23 | { path: '**', redirectTo: '/shop' } 24 | ]) 25 | ], 26 | providers: [], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | module.exports = function(){ 2 | return { 3 | products: [ 4 | {id: 1, name:"Iphone 5S", price: 1000, imageUrl:'1.jpg', description: 'Good phone', category: 'Phone'}, 5 | {id: 2, name:"Iphone 6S", price: 2000, imageUrl:'2.jpg', description: 'Good phone', category: 'Phone'}, 6 | {id: 3, name:"Iphone 7S", price: 3000, imageUrl:'3.jpg', description: 'Good phone', category: 'Phone'}, 7 | {id: 4, name:"Iphone 8", price: 4000, imageUrl:'4.jpg', description: 'Good phone', category: 'Phone'}, 8 | {id: 5, name:"Iphone 9", price: 5000, imageUrl:'5.jpg', description: 'Good phone', category: 'Phone'}, 9 | {id: 6, name:"Iphone 10", price: 6000, imageUrl:'6.jpg', description: 'Good phone', category: 'Computer'}, 10 | {id: 7, name:"Iphone 11", price: 7000, imageUrl:'7.jpg', description: 'Good phone', category: 'Computer'}, 11 | ], 12 | categories: [ 13 | {id: 1, name: 'Phone'}, 14 | {id: 2, name: 'Computer'}, 15 | {id: 3, name: 'Electronics'}, 16 | ], 17 | orders: [] 18 | } 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/app/admin/products/product-form/product-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | import { Product } from 'src/app/model/product.model'; 5 | import { ProductRepository } from 'src/app/model/product.repository'; 6 | 7 | @Component({ 8 | selector: 'app-product-form', 9 | templateUrl: './product-form.component.html', 10 | styleUrls: ['./product-form.component.css'] 11 | }) 12 | export class ProductFormComponent { 13 | editing: boolean = false; 14 | product: Product = new Product(); 15 | 16 | constructor( 17 | private activeRoute: ActivatedRoute, 18 | private repository: ProductRepository, 19 | private router: Router 20 | ) { 21 | this.editing = activeRoute.snapshot.params['mode'] === 'edit'; 22 | if (this.editing) { 23 | this.product = repository.getProduct(activeRoute.snapshot.params['id']); 24 | } 25 | } 26 | 27 | save(form: NgForm) { 28 | this.repository.saveProduct(this.product); 29 | this.router.navigateByUrl('/admin/main/products'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | }); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'angular-shop-app'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app.title).toEqual('angular-shop-app'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.nativeElement as HTMLElement; 29 | expect(compiled.querySelector('.content span')?.textContent).toContain('angular-shop-app app is running!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/shop/shop.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { FormsModule } from "@angular/forms"; 3 | import { BrowserModule } from "@angular/platform-browser"; 4 | import { ModelModule } from "../model/model.module"; 5 | import { ShopComponent } from "./shop.component"; 6 | import { NavbarComponent } from './navbar/navbar.component'; 7 | import { CartSummaryComponent } from './cart-summary/cart-summary.component'; 8 | import { CartDetailComponent } from './cart-detail/cart-detail.component'; 9 | import { CheckoutComponent } from './checkout/checkout.component'; 10 | import { RouterModule } from "@angular/router"; 11 | import { ProductListComponent } from './product-list/product-list.component'; 12 | import { CategoryListComponent } from './category-list/category-list.component'; 13 | 14 | @NgModule({ 15 | imports: [ModelModule, BrowserModule, FormsModule, RouterModule], 16 | declarations: [ShopComponent, NavbarComponent, CartSummaryComponent, CartDetailComponent, CheckoutComponent, ProductListComponent, CategoryListComponent], 17 | exports: [ShopComponent, CartDetailComponent, CheckoutComponent], 18 | }) 19 | export class ShopModule { } -------------------------------------------------------------------------------- /src/app/admin/categories/category-form/category-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Category } from 'src/app/model/category.model'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | import { CategoryRepository } from 'src/app/model/category.repository'; 5 | import { NgForm } from '@angular/forms'; 6 | 7 | @Component({ 8 | selector: 'app-category-form', 9 | templateUrl: './category-form.component.html', 10 | styleUrls: ['./category-form.component.css'] 11 | }) 12 | export class CategoryFormComponent implements OnInit { 13 | 14 | editing: boolean = false; 15 | category: Category = new Category(); 16 | 17 | constructor(private activeRoute: ActivatedRoute, private repository: CategoryRepository, private router: Router) { 18 | this.editing = activeRoute.snapshot.params['mode'] == 'edit'; 19 | if (this.editing) { 20 | this.category = repository.getCategory(activeRoute.snapshot.params['id']); 21 | } 22 | } 23 | 24 | ngOnInit() { 25 | } 26 | 27 | save(form: NgForm) { 28 | this.repository.saveCategory(this.category); 29 | this.router.navigateByUrl('/admin/main/categories'); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/app/admin/admin.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { AdminRoutingModule } from './admin-routing.module'; 5 | import { AuthComponent } from './auth/auth.component'; 6 | import { AdminComponent } from './admin.component'; 7 | import { FormsModule } from '@angular/forms'; 8 | import { AuthGuard } from './auth.guard'; 9 | import { ProductListComponent } from './products/product-list/product-list.component'; 10 | import { ProductFormComponent } from './products/product-form/product-form.component'; 11 | import { CategoryListComponent } from './categories/category-list/category-list.component'; 12 | import { CategoryFormComponent } from './categories/category-form/category-form.component'; 13 | import { OrderListComponent } from './orders/order-list/order-list.component'; 14 | 15 | 16 | @NgModule({ 17 | declarations: [ 18 | AdminComponent, 19 | AuthComponent, 20 | ProductListComponent, 21 | ProductFormComponent, 22 | CategoryListComponent, 23 | CategoryFormComponent, 24 | OrderListComponent 25 | ], 26 | imports: [ 27 | CommonModule, 28 | FormsModule, 29 | AdminRoutingModule, 30 | ], 31 | providers: [AuthGuard] 32 | }) 33 | export class AdminModule { } 34 | -------------------------------------------------------------------------------- /auth-middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | const app_secret = "myappsecret"; 4 | const username = "admin"; 5 | const password = "secret"; 6 | 7 | module.exports = function (req, res, next) { 8 | 9 | if (req.url === '/login' && req.method == 'POST') { 10 | if (req.body.username == username && req.body.password == password) { 11 | let token = jwt.sign({ data: username, expiresIn: '1h' }, app_secret); 12 | res.json({ success: true, token: token }); 13 | } else { 14 | res.json({ success: false }); 15 | } 16 | res.end(); 17 | return; 18 | } else { 19 | if ((req.url.startsWith("/products") || req.url.startsWith("/categories")) && (req.method != 'GET')) { 20 | let token = req.headers['authorization']; 21 | 22 | if (token != null && token.startsWith('Bearer<')) { 23 | token = token.substring(7, token.length - 1); 24 | try { 25 | jwt.verify(token, app_secret); 26 | next(); 27 | return; 28 | } 29 | catch (err) { } 30 | } 31 | res.statusCode = 401; 32 | res.end(); 33 | return; 34 | } 35 | } 36 | next(); 37 | } -------------------------------------------------------------------------------- /src/app/shop/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /src/app/model/category.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnInit } from "@angular/core"; 2 | import { Category } from "./category.model"; 3 | import { RestService } from "./rest.service"; 4 | 5 | @Injectable() 6 | export class CategoryRepository implements OnInit { 7 | private categories: Category[] = []; 8 | 9 | constructor(private restService: RestService) { 10 | this.restService.getCategories().subscribe(categories => this.categories = categories); 11 | } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | getCategory(id: number): Category | undefined { 17 | return this.categories.find(i => i.id == id); 18 | } 19 | 20 | getCategories(): Category[] { 21 | return this.categories; 22 | } 23 | 24 | saveCategory(category: Category) { 25 | if (category.id == null || category.id == 0) { 26 | this.restService.addCategory(category) 27 | .subscribe(p => this.categories.push(p)); 28 | } else { 29 | this.restService.updateCategory(category) 30 | .subscribe(p => { 31 | this.categories.splice(this.categories.findIndex(p => p.id == category.id), 1, category); 32 | }); 33 | } 34 | } 35 | 36 | deleteCategory(category: Category) { 37 | this.restService.deleteCategory(category) 38 | .subscribe(p => { 39 | this.categories.splice(this.categories.findIndex(p => p.id == category.id), 1); 40 | }); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/app/shop/shop.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ProductRepository } from '../model/product.repository'; 3 | import { Product } from '../model/product.model'; 4 | import { Category } from '../model/category.model'; 5 | 6 | @Component({ 7 | selector: 'shop', 8 | templateUrl: 'shop.component.html' 9 | }) 10 | 11 | export class ShopComponent { 12 | public selectedCategory: Category = null; 13 | public productsPerPage = 3; 14 | public selectedPage = 1; 15 | public selectedProducts: Product[] = []; 16 | 17 | constructor( 18 | private productRepository: ProductRepository 19 | ) { } 20 | 21 | get products(): Product[] { 22 | let index = (this.selectedPage - 1) * this.productsPerPage; 23 | 24 | this.selectedProducts = this.productRepository.getProducts(this.selectedCategory) 25 | 26 | return this.selectedProducts.slice(index, index + this.productsPerPage); 27 | } 28 | 29 | get pageNumbers(): number[] { 30 | return Array(Math.ceil(this.productRepository 31 | .getProducts(this.selectedCategory).length / this.productsPerPage)) 32 | .fill(0) 33 | .map((a, i) => i + 1); 34 | } 35 | 36 | changePage(p: number) { 37 | this.selectedPage = p; 38 | } 39 | 40 | changePageSize(size: number) { 41 | this.productsPerPage = size; 42 | this.changePage(1); 43 | } 44 | 45 | getCategory(category: Category) { 46 | this.selectedCategory = category; 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-shop-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "json": "json-server data.js -p 3500 -m auth-middleware.js" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^15.1.0", 15 | "@angular/common": "^15.1.0", 16 | "@angular/compiler": "^15.1.0", 17 | "@angular/core": "^15.1.0", 18 | "@angular/forms": "^15.1.0", 19 | "@angular/platform-browser": "^15.1.0", 20 | "@angular/platform-browser-dynamic": "^15.1.0", 21 | "@angular/router": "^15.1.0", 22 | "@fortawesome/angular-fontawesome": "^0.12.1", 23 | "@fortawesome/fontawesome-free": "^6.2.1", 24 | "@fortawesome/fontawesome-svg-core": "^6.2.1", 25 | "@fortawesome/free-solid-svg-icons": "^6.2.1", 26 | "bootstrap": "^5.2.3", 27 | "rxjs": "~7.8.0", 28 | "tslib": "^2.3.0", 29 | "zone.js": "~0.12.0" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "^15.1.2", 33 | "@angular/cli": "~15.1.2", 34 | "@angular/compiler-cli": "^15.1.0", 35 | "@types/jasmine": "~4.3.0", 36 | "jasmine-core": "~4.5.0", 37 | "json-server": "^0.17.1", 38 | "jsonwebtoken": "^9.0.0", 39 | "karma": "~6.4.0", 40 | "karma-chrome-launcher": "~3.1.0", 41 | "karma-coverage": "~2.2.0", 42 | "karma-jasmine": "~5.1.0", 43 | "karma-jasmine-html-reporter": "~2.0.0", 44 | "typescript": "~4.9.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/admin/admin-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { AuthComponent } from './auth/auth.component'; 4 | import { AdminComponent } from './admin.component'; 5 | import { AuthGuard } from './auth.guard'; 6 | import { ProductFormComponent } from './products/product-form/product-form.component'; 7 | import { ProductListComponent } from './products/product-list/product-list.component'; 8 | import { CategoryListComponent } from './categories/category-list/category-list.component'; 9 | import { CategoryFormComponent } from './categories/category-form/category-form.component'; 10 | import { OrderListComponent } from './orders/order-list/order-list.component'; 11 | 12 | const routes: Routes = [ 13 | { path: 'auth', component: AuthComponent }, 14 | { 15 | path: 'main', component: AdminComponent, canActivate: [AuthGuard], 16 | children: [ 17 | { path: "products/:mode/:id", component: ProductFormComponent }, 18 | { path: "products/:mode", component: ProductFormComponent }, 19 | { path: "products", component: ProductListComponent }, 20 | { path: "categories/:mode/:id", component: CategoryFormComponent }, 21 | { path: "categories/:mode", component: CategoryFormComponent }, 22 | { path: "categories", component: CategoryListComponent }, 23 | { path: "orders", component: OrderListComponent } 24 | ] 25 | }, 26 | { path: '**', redirectTo: 'auth' } 27 | ]; 28 | 29 | @NgModule({ 30 | imports: [RouterModule.forChild(routes)], 31 | exports: [RouterModule] 32 | }) 33 | export class AdminRoutingModule { } -------------------------------------------------------------------------------- /src/app/model/cart.model.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Product } from "./product.model"; 3 | 4 | @Injectable() 5 | export class Cart { 6 | public items: CartItem[] = []; 7 | public itemCount: number = 0; 8 | public total: number = 0; 9 | 10 | addItem(product: Product, quantity: number = 1) { 11 | let item = this.items.find(i => i.product.id == product.id); 12 | if (item != undefined) { 13 | item.quantity += quantity; 14 | } else { 15 | this.items.push(new CartItem(product, quantity)) 16 | } 17 | this.calculate(); 18 | } 19 | 20 | updateQuantity(product: Product, quantity: number) { 21 | let item = this.items.find(i => i.product.id == product.id); 22 | if (item != undefined) { 23 | item.quantity = Number(quantity); 24 | } 25 | this.calculate(); 26 | } 27 | 28 | calculate() { 29 | this.itemCount = 0; 30 | this.total = 0; 31 | 32 | this.items.forEach(item => { 33 | this.itemCount += item.quantity; 34 | this.total += (item.quantity * item.product.price); 35 | }) 36 | } 37 | 38 | removeItem(id: number) { 39 | let index = this.items.findIndex(i => i.product.id == id); 40 | this.items.splice(index, 2); 41 | this.calculate(); 42 | } 43 | 44 | clear() { 45 | this.items = []; 46 | this.itemCount = 0; 47 | this.total = 0; 48 | } 49 | } 50 | 51 | export class CartItem { 52 | constructor( 53 | public product: Product, 54 | public quantity: number 55 | ) { } 56 | } -------------------------------------------------------------------------------- /src/app/shop/product-list/product-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{selectedProduct.name}} 4 |
5 |
6 |
7 | {{selectedProduct.name}} 8 |
9 |

10 | {{selectedProduct.description}} 11 |

12 | 13 | 15 |
16 |
17 | 18 |
19 |
20 |
21 | {{p.name}} 22 |
23 |

24 | {{p.name}} 25 |

26 | 27 | {{p.price}} 28 | 29 |
30 | 35 |
36 |
37 |
-------------------------------------------------------------------------------- /src/app/model/product.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnInit } from "@angular/core"; 2 | import { Category } from "./category.model"; 3 | import { Product } from "./product.model"; 4 | import { RestService } from "./rest.service"; 5 | 6 | @Injectable() 7 | export class ProductRepository implements OnInit { 8 | private products: Product[] = []; 9 | 10 | constructor(private restService: RestService) { 11 | this.restService.getProducts().subscribe(products => this.products = products); 12 | } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | getProduct(id: number): Product | undefined { 18 | return this.products.find(i => i.id == id); 19 | } 20 | 21 | getProducts(category: Category = null): Product[] { 22 | if (category) { 23 | return this.products.filter(p => p.category === category.name); 24 | } else { 25 | return this.products; 26 | } 27 | } 28 | 29 | saveProduct(product: Product) { 30 | if (product.id == null || product.id == 0) { 31 | this.restService.addProduct(product) 32 | .subscribe(p => this.products.push(p)); 33 | } else { 34 | this.restService.updateProduct(product) 35 | .subscribe(p => { 36 | this.products.splice(this.products.findIndex(p => p.id == product.id), 1, product); 37 | }); 38 | } 39 | } 40 | 41 | deleteProduct(product: Product) { 42 | this.restService.deleteProduct(product) 43 | .subscribe(p => { 44 | this.products.splice(this.products.findIndex(p => p.id == product.id), 1); 45 | }); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/app/shop/shop.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | {{selectedProducts.length}} products found. 16 |
17 | 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 | 35 |
36 |
37 |
38 |
39 |
40 |
-------------------------------------------------------------------------------- /src/app/shop/checkout/checkout.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Thanks

6 |

Your order has been recorded.

7 | 8 |
9 | 10 |
11 |
12 | 13 | 14 | Please enter your name. 15 |
16 |
17 | 18 | 19 | Please enter your address. 20 |
21 |
22 | 23 | 24 | Please enter your city. 25 |
26 |
27 | 28 | 29 | Please enter your phone. 30 |
31 |
32 | 33 | 34 | Please enter your email. 35 |
36 | 37 |
38 | 39 | 40 |
41 |
42 |
-------------------------------------------------------------------------------- /src/app/model/rest.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Category } from './category.model'; 5 | import { Order } from './order.model'; 6 | import { Product } from './product.model'; 7 | import { map } from 'rxjs/operators'; 8 | 9 | @Injectable() 10 | export class RestService { 11 | 12 | baseUrl: string = "http://localhost:3500/"; 13 | token: string; 14 | 15 | constructor(private http: HttpClient) { } 16 | 17 | getProducts(): Observable { 18 | return this.http.get(this.baseUrl + "products"); 19 | } 20 | 21 | getCategories(): Observable { 22 | return this.http.get(this.baseUrl + "categories"); 23 | } 24 | 25 | saveOrder(order: Order): Observable { 26 | return this.http.post(this.baseUrl + "orders", order); 27 | } 28 | 29 | addProduct(product: Product): Observable { 30 | return this.http.post(this.baseUrl + "products", product, { 31 | headers: new HttpHeaders({ 32 | "Authorization": `Bearer<${this.token}>` 33 | }) 34 | }); 35 | } 36 | 37 | addCategory(category: Category): Observable { 38 | return this.http.post(this.baseUrl + "categories", category, { 39 | headers: new HttpHeaders({ 40 | "Authorization": `Bearer<${this.token}>` 41 | }) 42 | }); 43 | } 44 | 45 | updateProduct(product: Product): Observable { 46 | return this.http.put(this.baseUrl + "products/", product.id, { 47 | headers: new HttpHeaders({ 48 | "Authorization": `Bearer<${this.token}>` 49 | }) 50 | }); 51 | } 52 | 53 | updateCategory(category: Category): Observable { 54 | return this.http.put(this.baseUrl + "categories/", category.id, { 55 | headers: new HttpHeaders({ 56 | "Authorization": `Bearer<${this.token}>` 57 | }) 58 | }); 59 | } 60 | 61 | deleteProduct(product: Product): Observable { 62 | return this.http.delete(this.baseUrl + "products/" + product.id, { 63 | headers: new HttpHeaders({ 64 | "Authorization": `Bearer<${this.token}>` 65 | }) 66 | }) 67 | } 68 | 69 | deleteCategory(category: Category): Observable { 70 | return this.http.delete(this.baseUrl + "categories/" + category.id, { 71 | headers: new HttpHeaders({ 72 | "Authorization": `Bearer<${this.token}>` 73 | }) 74 | }) 75 | } 76 | 77 | authentication(username: string, password: string): Observable { 78 | return this.http.post(this.baseUrl + 'login', { 79 | username: username, 80 | password: password 81 | }).pipe(map(response => { 82 | this.token = response.success ? response.token : null; 83 | console.log(this.token); 84 | return response.success; 85 | })); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/app/shop/cart-detail/cart-detail.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Shopping Cart

5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 31 | 32 | 37 | 38 | 39 |
ProductPriceQuantityTotal
23 | 24 | {{item.product.name}}{{item.product.price}} EUR 28 | 30 | {{item.quantity * item.product.price}} EUR 33 | 36 |
40 |
41 | 42 | 43 | 44 |
45 | There are no items in your cart
46 | 49 |
50 |
51 |
52 |
53 | 54 |
55 |

Shopping Summary

56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
Total{{cart.total}} EUR
65 | 66 |
67 | 70 | 73 |
74 |
75 |
76 |
77 |
-------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-shop-app": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/angular-shop-app", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": [ 20 | "zone.js" 21 | ], 22 | "tsConfig": "tsconfig.app.json", 23 | "assets": [ 24 | "src/favicon.ico", 25 | "src/assets" 26 | ], 27 | "styles": [ 28 | "src/styles.css", 29 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 30 | "node_modules/@fortawesome/fontawesome-free/css/all.css" 31 | ], 32 | "scripts": [] 33 | }, 34 | "configurations": { 35 | "production": { 36 | "budgets": [ 37 | { 38 | "type": "initial", 39 | "maximumWarning": "500kb", 40 | "maximumError": "1mb" 41 | }, 42 | { 43 | "type": "anyComponentStyle", 44 | "maximumWarning": "2kb", 45 | "maximumError": "4kb" 46 | } 47 | ], 48 | "outputHashing": "all" 49 | }, 50 | "development": { 51 | "buildOptimizer": false, 52 | "optimization": false, 53 | "vendorChunk": true, 54 | "extractLicenses": false, 55 | "sourceMap": true, 56 | "namedChunks": true 57 | } 58 | }, 59 | "defaultConfiguration": "production" 60 | }, 61 | "serve": { 62 | "builder": "@angular-devkit/build-angular:dev-server", 63 | "configurations": { 64 | "production": { 65 | "browserTarget": "angular-shop-app:build:production" 66 | }, 67 | "development": { 68 | "browserTarget": "angular-shop-app:build:development" 69 | } 70 | }, 71 | "defaultConfiguration": "development" 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "angular-shop-app:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "polyfills": [ 83 | "zone.js", 84 | "zone.js/testing" 85 | ], 86 | "tsConfig": "tsconfig.spec.json", 87 | "assets": [ 88 | "src/favicon.ico", 89 | "src/assets" 90 | ], 91 | "styles": [ 92 | "src/styles.css" 93 | ], 94 | "scripts": [] 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | --------------------------------------------------------------------------------