├── src ├── assets │ ├── .gitkeep │ ├── imgs │ │ ├── heme.jpg │ │ ├── logo.png │ │ ├── plates-header.jpg │ │ ├── mashiko-yaki-saucer.jpg │ │ ├── blue-stripe-stoneware-plate.jpg │ │ ├── hand-painted-blue-flat-dish.jpg │ │ ├── mashiko-yaki-green-small-plate.jpg │ │ └── mashiko-yaki-indigo-small-plate.jpg │ ├── mock-data │ │ └── products.json │ └── css │ │ └── styles.css ├── app │ ├── app.component.css │ ├── app.component.html │ ├── model │ │ ├── cart.ts │ │ └── product.ts │ ├── app.component.ts │ ├── pages │ │ ├── cart │ │ │ ├── cart-page.routes.ts │ │ │ ├── cart-page.module.ts │ │ │ ├── cart-page.component.ts │ │ │ ├── cart-base.component.ts │ │ │ ├── cart-popup │ │ │ │ ├── cart-popup.component.ts │ │ │ │ ├── cart-popup.component.html │ │ │ │ └── cart-popup.component.css │ │ │ ├── cart.component.spec.ts │ │ │ ├── cart-page.component.html │ │ │ └── cart-page.component.css │ │ ├── product │ │ │ ├── product.routes.ts │ │ │ ├── product.module.ts │ │ │ ├── product.component.html │ │ │ ├── product.component.ts │ │ │ ├── product.component.css │ │ │ └── product.component.spec.ts │ │ └── category │ │ │ ├── category.routes.ts │ │ │ ├── category.module.ts │ │ │ ├── category.component.ts │ │ │ ├── category.component.html │ │ │ ├── category.component.spec.ts │ │ │ └── category.component.css │ ├── services │ │ ├── products.service.ts │ │ └── cart.service.ts │ ├── shared │ │ └── shared.module.ts │ ├── app.routes.ts │ ├── components │ │ ├── topbar │ │ │ ├── top-bar.component.css │ │ │ └── topbar.component.ts │ │ └── quantity-control │ │ │ └── quantity-control.component.ts │ └── app.module.ts ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── typings.d.ts ├── tsconfig.app.json ├── index.html ├── main.ts ├── tsconfig.spec.json ├── test.ts └── polyfills.ts ├── screenshot.png ├── .editorconfig ├── e2e ├── tsconfig.e2e.json ├── app.po.ts └── app.e2e-spec.ts ├── tsconfig.json ├── .gitignore ├── protractor.conf.js ├── karma.conf.js ├── package.json ├── .angular-cli.json ├── README.md └── tslint.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/assets/imgs/heme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/heme.jpg -------------------------------------------------------------------------------- /src/assets/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/logo.png -------------------------------------------------------------------------------- /src/assets/imgs/plates-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/plates-header.jpg -------------------------------------------------------------------------------- /src/assets/imgs/mashiko-yaki-saucer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/mashiko-yaki-saucer.jpg -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/imgs/blue-stripe-stoneware-plate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/blue-stripe-stoneware-plate.jpg -------------------------------------------------------------------------------- /src/assets/imgs/hand-painted-blue-flat-dish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/hand-painted-blue-flat-dish.jpg -------------------------------------------------------------------------------- /src/assets/imgs/mashiko-yaki-green-small-plate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/mashiko-yaki-green-small-plate.jpg -------------------------------------------------------------------------------- /src/assets/imgs/mashiko-yaki-indigo-small-plate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrew-yangy/online-store/HEAD/src/assets/imgs/mashiko-yaki-indigo-small-plate.jpg -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nec mollis sem. Etiam id luctus libero. 7 | Vivamus vulputate urna eget velit iaculis, et interdum elit pellentesque. Duis porta nunc neque, nec volutpat erat lacinia a. 8 |
9 |
8 |
9 | Live Demo
10 |
11 | ## How to start
12 |
13 | You will need to clone the source code of online-store GitHub repository.
14 |
15 | `git clone https://github.com/ddvkid/online-store.git`
16 |
17 | After the repository is cloned, go inside of the repository directory and install dependencies:
18 |
19 | ```
20 | cd online-store
21 | npm install
22 | ```
23 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
24 |
25 | ## Build
26 |
27 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
28 |
29 | ## Running unit tests
30 |
31 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
32 | #### Units to be tested
33 | 1. Category Page
34 | * test shopping cart, add a duplicate item should increment the quantity for that item.
35 | 2. Product Page, test add to cart button
36 | * Add 1 first item.
37 | * When quantity is null.
38 | * Add duplicate item.
39 | 3. Cart Page
40 | * change quantity.
41 | * remove item.
42 | ## Running end-to-end tests
43 |
44 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
45 | Before running the tests make sure you are serving the app via `ng serve`.
46 | #### Scenarios to be tested
47 | * should display 6 products.
48 | * should display cart popup.
49 | * should be able to add product to cart from image hover button.
50 | * should be able to navigate to product page from image hover button.
51 | * should be able to remove product from cart popup.
52 | * should be able to navigate to cart page from cart popup.
53 | * should be able to add product to cart from product page.
54 | * should be able to remove product from cart page.
55 |
56 | ## Notes
57 | #### Why Angular?
58 | Angular is a complete solution for rapid front-end development, get started with Angular using angular cli is easy. And Angular is much easier and faster than AngularJs.
59 | React and Vue can be good options too.
60 | #### Why not redux or mobx?
61 | Actually I think redux and mobx would be better choices when it comes to shopping cart case, however as it is a very simple shopping cart without any backend code,
62 | redux or mobx might make things more complex and reduce the code readability. In the real world project of shopping cart, I would use them to manage data flow.
63 | #### Why not store shopping cart data?
64 | As storing shopping cart information could be complicated and depends on the login system and backend database.
65 | With not signed users, we have to save this data on web storage(Cookie, Session and Local Storage), and with signed users, we need update this
66 | to database. I am not able to implement that without having further information.
67 | #### Why Protractor and Karma
68 | They are in the Angular package, so why not :grin:
69 |
--------------------------------------------------------------------------------
/src/app/components/topbar/topbar.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by andrew.yang on 7/28/2017.
3 | */
4 | import { Component, OnInit } from '@angular/core';
5 | import {CartService} from "../../services/cart.service";
6 |
7 | @Component({
8 | selector: 'top-bar',
9 | styleUrls: ['./top-bar.component.css'],
10 | template: `
11 |
64 | `
65 | })
66 | export class TopbarComponent implements OnInit {
67 | public collapse: boolean = false;
68 | public cart_num:number;
69 | constructor(
70 | private cartService: CartService
71 | ) { }
72 |
73 | ngOnInit() {
74 | this.cartService.cartListSubject
75 | .subscribe(res => {
76 | this.cart_num = res.length;
77 | })
78 | }
79 | toggleCartPopup = (event) => {
80 | event.preventDefault();
81 | event.stopPropagation();
82 | this.cartService.toggleCart()
83 | }
84 | }
--------------------------------------------------------------------------------
/src/app/pages/cart/cart-popup/cart-popup.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | width: 400px;
4 | position: absolute;
5 | right: 0;
6 | text-transform: uppercase;
7 | -webkit-box-shadow: 0 2px 3px rgba(128,128,128,.25);
8 | -moz-box-shadow:0 2px 3px rgba(128,128,128,.25);
9 | box-shadow: 0 2px 3px rgba(128,128,128,.25);
10 | background: white;
11 | transition: all .5s cubic-bezier(.25,.46,.45,.94);
12 | top: -1000px;
13 | opacity: 0;
14 | max-height: calc(100vh - 120px);
15 | overflow-y: auto;
16 | }
17 |
18 | :host.visible {
19 | top: 100%;
20 | opacity: 1;
21 | }
22 | .cart-empty {
23 | float:right;
24 | margin-right: 20px;
25 | }
26 | .quick-cart-footer,
27 | .pop-cart-item {
28 | display: table;
29 | border-spacing: 15px 15px;
30 | border-collapse: separate;
31 | box-sizing: border-box;
32 | table-layout: fixed;
33 | width: 100%;
34 | }
35 | .pop-cart-item-image-wrapper {
36 | width: 80px;
37 | display: table-cell;
38 | vertical-align: top;
39 | }
40 | .pop-cart-item-image {
41 | color: #1d1d20;
42 | vertical-align: middle;
43 | display: block;
44 | width: 80px;
45 | height: 80px;
46 | background-position: 50% 50%;
47 | background-repeat: no-repeat;
48 | background-size: cover;
49 | }
50 | .pop-cart-item-details {
51 | display: table-cell;
52 | vertical-align: top;
53 | }
54 | .pop-cart-item-title {
55 | margin: 0;
56 | font-size: 13px;
57 | line-height: 1.2;
58 | text-transform: uppercase;
59 | font-weight: 700;
60 | font-family: Roboto;
61 | }
62 | .pop-cart-item-title a {
63 | color: black;
64 | letter-spacing: .1em;
65 | text-decoration: none;
66 | }
67 | .pop-cart-item-title a:hover {
68 | color: #99999b;
69 | }
70 | .pop-cart-item-quantity {
71 | display: inline-block;
72 | margin-left: 5px;
73 | font-size: .76923em;
74 | font-weight: 400;
75 | }
76 | .pop-cart-item-brand {
77 | display: block;
78 | margin-top: 4px;
79 | margin-bottom: 2px;
80 | font-size: .76923em;
81 | line-height: 1.3;
82 | font-family: Roboto;
83 | letter-spacing: .05em;
84 | color: #9f9f9f;
85 | text-transform: uppercase;
86 | font-weight: 700;
87 | }
88 | .pop-cart-item-price {
89 | display: block;
90 | font-size: .92857em;
91 | font-weight: 700;
92 | }
93 | .pop-cart-remove-wrapper {
94 | width: 18px;
95 | height: 20px;
96 | text-align: center;
97 | display: table-cell;
98 | vertical-align: top;
99 | }
100 | .pop-cart-remove-wrapper span{
101 | cursor: pointer;
102 | }
103 | .cart-total {
104 | padding: 0 15px;
105 | }
106 | .cart-total-item {
107 | padding-top: 15px;
108 | border-top: 1px solid #e4e4e4;
109 | max-width: 1140px;
110 | margin-left: auto;
111 | margin-right: auto;
112 | font-size: .85714em;
113 | color: #9f9f9f;
114 | font-weight: 400;
115 | letter-spacing: .1em;
116 | }
117 | .cart-total-label {
118 | float: left;
119 | }
120 | .cart-total-value {
121 | float: right;
122 | }
123 | .quick-cart-footer {
124 | display: table;
125 | width: 100%;
126 | border-bottom: 1px solid #e4e4e4;
127 | }
128 | .quick-cart-footer-cell {
129 | display: table-cell;
130 | }
--------------------------------------------------------------------------------
/src/app/pages/cart/cart-page.component.html:
--------------------------------------------------------------------------------
1 |