├── public
├── favicon.ico
├── images
│ ├── Screenshot (17).png
│ ├── Screenshot (18).png
│ ├── Screenshot (19).png
│ ├── Screenshot (20).png
│ └── Screenshot (21).png
├── manifest.json
└── index.html
├── src
├── components
│ ├── header
│ │ ├── stylesheets
│ │ │ ├── dropList.css
│ │ │ ├── userHeader.module.sass
│ │ │ ├── menu.module.sass
│ │ │ ├── search.module.sass
│ │ │ └── header.module.sass
│ │ ├── components
│ │ │ ├── DropList.js
│ │ │ ├── Search.js
│ │ │ ├── UserHeader.js
│ │ │ └── Menu.js
│ │ ├── headerContainer.js
│ │ └── Header.js
│ ├── loadingAnimation
│ │ ├── loading.module.sass
│ │ └── index.js
│ └── autoComplete
│ │ ├── AutoCompleteContainer.js
│ │ ├── auto.module.sass
│ │ └── AutoComplete.js
├── assets
│ └── images
│ │ ├── background.jpg
│ │ ├── loading-bars.svg
│ │ ├── filter.svg
│ │ └── checkmark.svg
├── pages
│ ├── loginsignin
│ │ ├── utils
│ │ │ ├── capitalizeString.js
│ │ │ └── validation.js
│ │ ├── components
│ │ │ ├── footer.js
│ │ │ ├── Button.js
│ │ │ ├── Base.js
│ │ │ └── FormInput.js
│ │ ├── LoginContainer.js
│ │ ├── SigninContainer.js
│ │ ├── stylesheets
│ │ │ ├── loginsignin.module.sass
│ │ │ ├── base.module.sass
│ │ │ └── formInput.module.sass
│ │ ├── Login.js
│ │ ├── Signin.js
│ │ └── LoginSignin.js
│ ├── dashboard
│ │ ├── utils
│ │ │ ├── findPropWithRegex.js
│ │ │ └── generateFilterString.js
│ │ ├── components
│ │ │ ├── Selection.js
│ │ │ ├── Checkbox.js
│ │ │ ├── SubSelection.js
│ │ │ ├── Product.js
│ │ │ ├── Filter_md.js
│ │ │ └── Filter.js
│ │ ├── stylesheets
│ │ │ ├── product.module.sass
│ │ │ ├── selection.module.sass
│ │ │ ├── dashboard.module.sass
│ │ │ ├── sub_selection.module.sass
│ │ │ ├── filter.module.sass
│ │ │ ├── checkbox.module.sass
│ │ │ └── filter_md.module.sass
│ │ ├── DashboardContainer.js
│ │ └── Dashboard.js
│ ├── checkoutCancel
│ │ └── CheckoutCancel.js
│ ├── checkout
│ │ ├── utils
│ │ │ └── calcPrice.js
│ │ ├── stylesheets
│ │ │ ├── subtotal.module.sass
│ │ │ ├── table.module.sass
│ │ │ └── checkout.module.sass
│ │ ├── checkoutContainer.js
│ │ ├── components
│ │ │ ├── CheckoutTable.js
│ │ │ └── Subtotal.js
│ │ └── Checkout.js
│ ├── productOverview
│ │ ├── utils
│ │ │ └── mergeProductAndVariants.js
│ │ ├── stylesheets
│ │ │ ├── sizes.module.sass
│ │ │ ├── variants.module.sass
│ │ │ └── productOverview.module.sass
│ │ ├── ProductOverviewContainer.js
│ │ ├── components
│ │ │ ├── Sizes.js
│ │ │ └── Variants.js
│ │ └── ProductOverview.js
│ ├── checkoutSuccess
│ │ ├── utils
│ │ │ └── mapSearchURL.js
│ │ ├── CheckoutSuccessContainer.js
│ │ ├── stylesheet
│ │ │ └── checkoutSuccess.module.sass
│ │ └── CheckoutSuccess.js
│ └── shoppingBag
│ │ ├── ShoppingBagContainer.js
│ │ ├── stylesheets
│ │ ├── table.module.sass
│ │ └── shoppingBag.module.sass
│ │ ├── ShoppingBag.js
│ │ └── components
│ │ └── Table.js
├── configs
│ └── paypalConfig.js
├── modules
│ ├── Navigation
│ │ └── index.js
│ ├── Auth
│ │ └── index.js
│ ├── mediaQuery
│ │ ├── _app.sass
│ │ └── index.js
│ └── serverCall
│ │ └── index.js
├── App.test.js
├── index.css
├── redux
│ ├── store
│ │ └── index.js
│ ├── reducer
│ │ ├── index.js
│ │ ├── signinReducer.js
│ │ ├── departmentReducer.js
│ │ ├── filterReducer.js
│ │ ├── variantsReducer.js
│ │ ├── tokenReducer.js
│ │ ├── checkoutReducer.js
│ │ ├── cartReducer.js
│ │ └── productReducer.js
│ └── action
│ │ ├── filterAction.js
│ │ ├── departmentAction.js
│ │ ├── variantsAction.js
│ │ ├── signinAction.js
│ │ ├── tokenAction.js
│ │ ├── cartAction.js
│ │ ├── checkoutAction.js
│ │ └── productAction.js
├── index.js
├── App.css
├── App.js
├── logo.svg
└── serviceWorker.js
├── .gitignore
├── package.json
├── LICENSE
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/components/header/stylesheets/dropList.css:
--------------------------------------------------------------------------------
1 | .nav-link {
2 | padding: 0;
3 | color: white;
4 | }
--------------------------------------------------------------------------------
/src/assets/images/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/src/assets/images/background.jpg
--------------------------------------------------------------------------------
/public/images/Screenshot (17).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/public/images/Screenshot (17).png
--------------------------------------------------------------------------------
/public/images/Screenshot (18).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/public/images/Screenshot (18).png
--------------------------------------------------------------------------------
/public/images/Screenshot (19).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/public/images/Screenshot (19).png
--------------------------------------------------------------------------------
/public/images/Screenshot (20).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/public/images/Screenshot (20).png
--------------------------------------------------------------------------------
/public/images/Screenshot (21).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/levelopers/Ecommerce-Reactjs/HEAD/public/images/Screenshot (21).png
--------------------------------------------------------------------------------
/src/pages/loginsignin/utils/capitalizeString.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const capitalizeString=(str)=>{
4 | return str.charAt(0).toUpperCase()+str.slice(1)
5 | }
6 |
7 | export default capitalizeString
--------------------------------------------------------------------------------
/src/pages/dashboard/utils/findPropWithRegex.js:
--------------------------------------------------------------------------------
1 | export default (object,regex)=>{
2 | for (const key in object) {
3 | if (key.match(regex)) {
4 | return key
5 | }
6 | }
7 | return undefined
8 | }
--------------------------------------------------------------------------------
/src/pages/checkoutCancel/CheckoutCancel.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect } from 'react-router-dom';
3 |
4 | export default function CheckoutCancel() {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/src/configs/paypalConfig.js:
--------------------------------------------------------------------------------
1 | // Paypal sandbox api (https://developer.paypal.com/docs/api/overview/)
2 | // replace empty string with your 'Client ID' and 'Secret'
3 | export default {
4 | username: '',
5 | password: ''
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/checkout/utils/calcPrice.js:
--------------------------------------------------------------------------------
1 |
2 | const calcPrice=(subtotal)=>{
3 | return {
4 | taxes:parseFloat((subtotal*0.13).toFixed(2)),
5 | total:parseFloat((subtotal*1.13).toFixed(2))
6 | }
7 | }
8 | export default calcPrice
--------------------------------------------------------------------------------
/src/pages/productOverview/utils/mergeProductAndVariants.js:
--------------------------------------------------------------------------------
1 | export default (product,variants)=>{
2 | let resultArray=[product||{}]
3 |
4 | for (const v of variants||[]) {
5 | resultArray.push(v||{})
6 | }
7 | return resultArray
8 | }
--------------------------------------------------------------------------------
/src/modules/Navigation/index.js:
--------------------------------------------------------------------------------
1 |
2 | let history
3 | export const registerNav = (ref) => {
4 | history = ref.history
5 | }
6 |
7 | const jumpTo = (uri) => {
8 | history.push(uri)
9 | }
10 | export const go=(uri)=>{
11 | history.go(uri)
12 | }
13 | export default jumpTo
14 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/components/footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const style={
4 | fontSize:'15px',
5 | marginBottom:'30px'
6 | }
7 |
8 | export default function footer({content}) {
9 | return (
10 |
11 | {content}
12 |
13 | )
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/pages/productOverview/stylesheets/sizes.module.sass:
--------------------------------------------------------------------------------
1 | @import '~bootstrap/scss/_functions'
2 | @import '~bootstrap/scss/_variables'
3 | @import '~bootstrap/scss/mixins/_breakpoints'
4 |
5 |
6 | @include media-breakpoint-down(sm)
7 | .btn
8 | button
9 | font-size: 0.5rem
10 | .item
11 | font-size: 0.5rem!important
--------------------------------------------------------------------------------
/src/pages/checkoutSuccess/utils/mapSearchURL.js:
--------------------------------------------------------------------------------
1 |
2 | const mapSearchURL = (searchUrl) => {
3 | const map = new Map()
4 | let queryArr = searchUrl.replace('?', '').split('&')
5 | for (const k of queryArr) {
6 | let pairArr = k.split('=')
7 | map.set(pairArr[0], pairArr[1])
8 | }
9 | return map
10 | }
11 | export default mapSearchURL
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/loadingAnimation/loading.module.sass:
--------------------------------------------------------------------------------
1 | .loading_background
2 | position: absolute
3 | top: 0
4 | left: 0
5 | width: 100vw
6 | height: 100vh
7 | background: rgba(125,125,125,0.5)
8 | color: blue
9 | display: flex
10 | justify-content: center
11 | align-items: center
12 | z-index: 2
13 |
14 | .svg
15 | width: 10%
16 | height: 10%
17 | // margin-top: 20%
--------------------------------------------------------------------------------
/src/components/loadingAnimation/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import loading_animation from '../../assets/images/loading-bars.svg'
3 | import styles from './loading.module.sass'
4 | export default function index() {
5 | return (
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/LoginContainer.js:
--------------------------------------------------------------------------------
1 | import {connect } from 'react-redux'
2 | import Login from './Login'
3 | import {postToken} from '../../redux/action/tokenAction'
4 | const mapDispatchToProps={
5 | postToken
6 | }
7 | const mapStoreToProps=state=>({
8 | login_loading:state.token.token_loading,
9 | login_error:state.token.error
10 | })
11 |
12 | export default connect(mapStoreToProps,mapDispatchToProps)(Login)
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | /src/configs
4 |
5 | # dependencies
6 | /node_modules
7 | /.pnp
8 | .pnp.js
9 |
10 | # testing
11 | /coverage
12 |
13 | # production
14 | /build
15 |
16 | # misc
17 | .DS_Store
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/SigninContainer.js:
--------------------------------------------------------------------------------
1 | import Signin from './Signin'
2 | import { connect } from 'react-redux'
3 | import { signin } from '../../redux/action/signinAction'
4 |
5 | const mapDispatchToProps = {
6 | signin
7 | }
8 | const mapStoreToProps = state => ({
9 | signin_loading: state.signin.signin_loading,
10 | signin_error: state.signin.error
11 | })
12 |
13 | export default connect(mapStoreToProps, mapDispatchToProps)(Signin)
14 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/stylesheets/loginsignin.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Roboto')
2 |
3 | .outbox
4 | width: 100vw
5 | height: 100vh
6 | background-image: url(../../../assets/images/background.jpg)
7 | background-size: cover
8 | display: flex
9 | justify-content: center
10 |
11 | .box
12 | width: 20%
13 | min-width: 300px
14 | max-height: 50%
15 | margin-top: 5%
16 | border-radius: 5px
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/stylesheets/base.module.sass:
--------------------------------------------------------------------------------
1 | .outbox
2 | display: flex
3 | flex-direction: column
4 | align-items: center
5 | background: white
6 |
7 | .logo
8 | width: 200px
9 | height: 130px
10 | background: rgb(238,130,238)
11 | font-size: 50px
12 | color: white
13 |
14 | .title_style
15 | color: blue
16 | width: 80%
17 | font-size: 20px
18 |
19 | .border_style
20 | width: 80%
21 | border-bottom: 1px solid blue
22 | margin-bottom: 5px
23 |
--------------------------------------------------------------------------------
/src/redux/store/index.js:
--------------------------------------------------------------------------------
1 | import rootReducer from '../reducer'
2 | import {createStore, applyMiddleware, compose} from 'redux'
3 | import thunk from 'redux-thunk'
4 |
5 | const initialState={}
6 | const middlewares=[thunk]
7 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
8 |
9 | const store=createStore(
10 | rootReducer,
11 | initialState,
12 | composeEnhancers(
13 | applyMiddleware(...middlewares)
14 | )
15 | )
16 |
17 | export default store
--------------------------------------------------------------------------------
/src/pages/dashboard/components/Selection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/selection.module.sass'
3 | export default function Selection({ title, selected,category, onChange }) {
4 | return (
5 | onChange(e,category, title)}
8 | >
9 |
10 | {title}
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/product.module.sass:
--------------------------------------------------------------------------------
1 | @import "node_modules/bootstrap/scss/functions";
2 | @import "node_modules/bootstrap/scss/variables";
3 | @import "node_modules/bootstrap/scss/mixins/_breakpoints";
4 |
5 | .card
6 | min-width: 150px
7 |
8 | @include media-breakpoint-down(sm)
9 | .title
10 | font-size: 15px
11 | .subtitle
12 | font-size: 10px!important
13 | .color
14 | font-size: 10px
15 |
16 | // .card
17 |
18 | // .image
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/autoComplete/AutoCompleteContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import AutoComplete from './AutoComplete'
3 | import { filter } from '../../redux/action/filterAction'
4 |
5 | const mapStoreToProps = state => ({
6 | filter_result: state.filter.filter_result,
7 | error: state.filter.error,
8 | loading: state.filter.loading
9 | })
10 |
11 | const mapDispatchToProps = {
12 | filter
13 | }
14 |
15 | export default connect(mapStoreToProps, mapDispatchToProps)(AutoComplete)
--------------------------------------------------------------------------------
/src/modules/Auth/index.js:
--------------------------------------------------------------------------------
1 | class Auth {
2 | constructor() {
3 | this.user_token = JSON.parse(localStorage.getItem('auth'))||{}
4 | }
5 | getToken() {
6 | return this.user_token.token
7 | }
8 | getUserId() {
9 | return this.user_token.user_id
10 | }
11 | setUserToken(new_token) {
12 | this.user_token = new_token
13 | localStorage.setItem('auth', JSON.stringify(new_token))
14 | }
15 | logout() {
16 | localStorage.removeItem('auth')
17 | }
18 | }
19 | export default new Auth()
--------------------------------------------------------------------------------
/src/pages/checkoutSuccess/CheckoutSuccessContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import CheckoutSuccess from './CheckoutSuccess'
3 | import { getPayment } from '../../redux/action/checkoutAction'
4 |
5 | const mapStoreToProps = state => ({
6 | payment: state.checkout.payment
7 | })
8 | const mapDispatchToProps = dispatch => ({
9 | getPayment: (paymentId, payerId) => dispatch(getPayment(paymentId, payerId))
10 | })
11 |
12 | export default connect(mapStoreToProps, mapDispatchToProps)(CheckoutSuccess)
--------------------------------------------------------------------------------
/src/pages/shoppingBag/ShoppingBagContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import ShoppingBag from './ShoppingBag'
3 | import {getCartByUserId,postCart} from '../../redux/action/cartAction'
4 |
5 | const mapStoreToProps=state=>({
6 | cart:state.cart.cart
7 | })
8 | const mapDispatchToProps=dispatch=>({
9 | getCartByUserId:dispatch(getCartByUserId()),
10 | postCart:(pid,increase,decrease)=>dispatch(postCart(pid,increase,decrease))
11 | })
12 |
13 | export default connect(mapStoreToProps,mapDispatchToProps)(ShoppingBag)
--------------------------------------------------------------------------------
/src/pages/checkout/stylesheets/subtotal.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | .outbox
4 | display: flex
5 | flex-direction: column
6 | font-size: 1em
7 |
8 | .subtotals
9 | display: flex
10 | flex-direction: column
11 | .row
12 | display: flex
13 | justify-content: space-between
14 | font-size: 1em
15 | color: grey
16 | margin: 0.1em 0
17 | .total
18 | font-size: 1.2em
19 | color: black
20 | font-weight: 500
21 |
22 | @media screen and (max-width: $tablet)
23 | .outbox
24 | font-size: .7rem
--------------------------------------------------------------------------------
/src/redux/reducer/index.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux'
2 | import token from './tokenReducer'
3 | import signin from './signinReducer'
4 | import department from './departmentReducer'
5 | import product from './productReducer'
6 | import variant from './variantsReducer'
7 | import cart from './cartReducer'
8 | import checkout from './checkoutReducer'
9 | import filter from './filterReducer'
10 |
11 | export default combineReducers({
12 | token,
13 | signin,
14 | department,
15 | product,
16 | variant,
17 | cart,
18 | checkout,
19 | filter
20 | })
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/selection.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | @media screen and (max-width: $tablet)
4 | .sub_title
5 | height: 100%
6 | display: flex
7 | align-items: center
8 | padding: 2px 0
9 | &:hover, &:focus
10 | border-bottom: 1px solid blue
11 | cursor: pointer
12 |
13 | .selected
14 | border-bottom: 1px solid blue
15 |
16 | .title
17 | font-size: .8rem
18 | padding: 0 .5rem
19 |
20 | @media screen and (max-width: $mobileL)
21 | .title
22 | font-size: .5rem
23 |
--------------------------------------------------------------------------------
/src/pages/dashboard/DashboardContainer.js:
--------------------------------------------------------------------------------
1 | import { getAllProducts, applyFilters } from '../../redux/action/productAction'
2 | import { connect } from 'react-redux'
3 | import Dashboard from './Dashboard'
4 | const mapStoreToProps = state => ({
5 | products: state.product.products,
6 | loading:state.product.loading
7 | })
8 | const mapDispatchToProps = dispatch => ({
9 | getAllProducts: ()=>dispatch(getAllProducts()),
10 | applyFilters:(filter_string)=>dispatch(applyFilters(filter_string))
11 | })
12 |
13 | export default connect(mapStoreToProps, mapDispatchToProps)(Dashboard)
--------------------------------------------------------------------------------
/src/pages/dashboard/components/Checkbox.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/checkbox.module.sass'
3 |
4 | export default function Checkbox({ onChange, name, category, isChecked }) {
5 | return (
6 |
7 |
onChange(e, category)}
12 | checked={isChecked}
13 | />
14 |
15 |
16 | {name}
17 |
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 | import {Provider} from 'react-redux'
7 | import store from './redux/store'
8 |
9 | ReactDOM.render( , document.getElementById('root'));
10 |
11 | // If you want your app to work offline and load faster, you can change
12 | // unregister() to register() below. Note this comes with some pitfalls.
13 | // Learn more about service workers: https://bit.ly/CRA-PWA
14 | serviceWorker.unregister();
15 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/stylesheets/formInput.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans')
2 |
3 | .outbox
4 | display: flex
5 | flex-direction: column
6 | width: 70%
7 | // height: 70px
8 | margin-bottom: 5px
9 |
10 | .label
11 | font-family: 'Open Sans', sans-serif
12 | font-size: 15px
13 | label
14 | margin: 0
15 |
16 | .input
17 | width: 100%
18 | margin-bottom: 3px
19 | input
20 | width: 100%
21 | height: 100%
22 | border-radius: 5px
23 | border: 1px solid grey
24 | padding: 0 0.5rem
25 |
26 | .errMsg
27 | color: red
28 | font-size: 15px
--------------------------------------------------------------------------------
/src/pages/productOverview/ProductOverviewContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import ProductOverview from './ProductOverview'
3 | import {getProduct} from '../../redux/action/productAction'
4 | import {getVariantsByProductId} from '../../redux/action/variantsAction'
5 | import {postCart} from '../../redux/action/cartAction'
6 |
7 | const mapStoreToProps=state=>({
8 | product:state.product.product,
9 | variants:state.variant.variants
10 | })
11 | const mapDispatchToProps={
12 | getProduct,
13 | getVariantsByProductId,
14 | postCart
15 | }
16 |
17 | export default connect(mapStoreToProps,mapDispatchToProps)(ProductOverview)
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | .App-header {
12 | background-color: #282c34;
13 | min-height: 100vh;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | justify-content: center;
18 | font-size: calc(10px + 2vmin);
19 | color: white;
20 | }
21 |
22 | .App-link {
23 | color: #61dafb;
24 | }
25 |
26 | @keyframes App-logo-spin {
27 | from {
28 | transform: rotate(0deg);
29 | }
30 | to {
31 | transform: rotate(360deg);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/header/stylesheets/userHeader.module.sass:
--------------------------------------------------------------------------------
1 | .outbox
2 | display: flex
3 | justify-content: flex-end
4 | background: rgb(65,65,65)
5 | height: 4vh
6 |
7 | .tag
8 | padding: 0.5rem
9 | white-space: nowrap
10 | color: white
11 | background: #5E7264
12 | display: flex
13 | align-items: center
14 | justify-content: center
15 | &:hover
16 | cursor: pointer
17 | color: #0056b3
18 |
19 | .loggout
20 | display: flex
21 | align-items: center
22 | margin-right: 2rem
23 | margin-left: 1rem
24 |
25 | .login
26 | color: white
27 | font-size: 1rem
28 | &:hover
29 | cursor: pointer
30 | color: blue
--------------------------------------------------------------------------------
/src/components/header/components/DropList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NavDropdown } from 'react-bootstrap'
3 | import jumpTo from '../../../modules/Navigation'
4 | import '../stylesheets/dropList.css'
5 |
6 | export default function DropList({ department, categories,clickCategory }) {
7 | return (
8 |
9 | {categories && categories.map(c =>
10 | {
12 | clickCategory(c)
13 | jumpTo('/dashboard')
14 | }}
15 | key={c}>{c}
16 |
17 | )}
18 |
19 | )
20 | }
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/pages/checkout/checkoutContainer.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Checkout from './Checkout'
3 | import {getCheckoutUrl, getPaypalUrl} from '../../redux/action/checkoutAction'
4 | import {getCartByUserId} from '../../redux/action/cartAction'
5 |
6 | const mapStoreToProps = state => ({
7 | cart:state.cart.cart,
8 | url:state.checkout.approval_url,
9 | name:state.token.user_token.user_name
10 | })
11 | const mapDispatchToProps = dispatch => ({
12 | getCheckoutUrl:(cartId)=>dispatch(getCheckoutUrl(cartId)),
13 | getCartByUserId:()=>dispatch(getCartByUserId())
14 | })
15 |
16 | export default connect(mapStoreToProps, mapDispatchToProps)(Checkout)
--------------------------------------------------------------------------------
/src/pages/loginsignin/components/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const outStyle={
4 | width:'80%',
5 | height:'30px',
6 | marginBottom:'10px',
7 | marginTop:'5px'
8 | }
9 | const btnStyle={
10 | width:'100%',
11 | height:'100%',
12 | borderRadius:'7px',
13 | background:'rgb(0,100,255)',
14 | color:'white',
15 | fontFamily:'Roboto sans-serif',
16 | fontSize:'15px'
17 | }
18 |
19 | export default function Button({ button_title,onClick }) {
20 | return (
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/pages/productOverview/components/Sizes.js:
--------------------------------------------------------------------------------
1 | import { Dropdown, DropdownButton } from 'react-bootstrap'
2 | import React from 'react'
3 | import styles from '../stylesheets/sizes.module.sass'
4 | export default function Sizes({ sizes, selectedSize, clickSize }) {
5 | return (
6 |
7 |
8 | {sizes.slice(1).length > 0
9 | ? sizes.slice(1).map(s =>
10 | clickSize(s)} key={s}>{s}
11 | )
12 | : "no more sizes"
13 | }
14 |
15 |
16 | )
17 | }
--------------------------------------------------------------------------------
/src/redux/action/filterAction.js:
--------------------------------------------------------------------------------
1 | import serverCall from '../../modules/serverCall'
2 |
3 | export const filter=(text)=>dispatch=>{
4 | dispatch({
5 | type:FILTER_BEGIN,
6 | })
7 | return serverCall({
8 | method:'GET',
9 | url:`/filter?query=${text}`
10 | })
11 | .then(res=>{
12 | dispatch({
13 | type: FILTER_SUCCESS,
14 | payload: res
15 | })
16 | return res
17 | })
18 | .catch(error=>{
19 | dispatch({
20 | type: FILTER_FAIL,
21 | payload: {error}
22 | })
23 | return error
24 | })
25 | }
26 |
27 | export const FILTER_BEGIN='FILTER_BEGIN'
28 | export const FILTER_SUCCESS='FILTER_SUCCESS'
29 | export const FILTER_FAIL='FILTER_FAIL'
--------------------------------------------------------------------------------
/src/pages/checkout/components/CheckoutTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/table.module.sass'
3 |
4 | export default function CheckoutTable({ items }) {
5 | return (
6 |
7 | {Object.keys(items).map(i =>
8 |
9 |
10 |
11 |
12 |
13 | {items[i].item.title}
14 |
15 |
16 | ${items[i].item.price} X {items[i].qty}
17 |
18 |
19 | )}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/header/headerContainer.js:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux'
2 | import Header from './Header'
3 | import {getDepartments} from '../../redux/action/departmentAction'
4 | import {getProductsByCategory,getAllProducts,search} from '../../redux/action/productAction'
5 |
6 |
7 | const mapStoreToProps=state=>({
8 | user_token:state.token.user_token,
9 | departments:state.department.departments,
10 | })
11 |
12 | const mapDispatchToProps=dispatch=>({
13 | getDepartments:dispatch(getDepartments()),
14 | search:(t)=>dispatch(search(t)),
15 | getProductsByCategory:(c)=>dispatch(getProductsByCategory(c)),
16 | getAllProducts:()=>dispatch(getAllProducts())
17 | })
18 |
19 | export default connect(mapStoreToProps,mapDispatchToProps)(Header)
--------------------------------------------------------------------------------
/src/pages/dashboard/components/SubSelection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/sub_selection.module.sass'
3 |
4 | export default function SubSelection({ selected_arr, onClick, content, category }) {
5 | return (
6 |
7 |
8 | {content.map(c => {
9 | c = c.toUpperCase()
10 | return (
11 |
onClick(e, category, c)}>
15 | {c}
16 |
17 | )
18 | })}
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/header/stylesheets/menu.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 |
4 | .outbox
5 | display: flex
6 | margin: auto
7 | color: white !important
8 |
9 | .lists
10 | display: flex
11 | word-break: none
12 | white-space: nowrap
13 |
14 | .tag
15 | padding: 0.8rem
16 | white-space: nowrap
17 | display: flex
18 | align-items: center
19 | // color: #007bff
20 | &:hover
21 | cursor: pointer
22 | color: #0056b3
23 |
24 | @media screen and (max-width: $tablet)
25 | .lists
26 | display: flex
27 | flex-direction: column
28 |
29 | .outbox
30 | display: flex
31 | flex-direction: column-reverse
32 | margin: 0
33 | font-size: .8rem
34 |
35 | .tag
36 | border-bottom: 1px solid rgba(0,0,0,.5)
--------------------------------------------------------------------------------
/src/redux/action/departmentAction.js:
--------------------------------------------------------------------------------
1 | import serverCall from '../../modules/serverCall'
2 |
3 | export const getDepartments=()=>dispatch=>{
4 | dispatch({
5 | type:GET_DEPARTMENTS_BEGIN,
6 | })
7 | return serverCall({
8 | method:'GET',
9 | url:'/departments'
10 | })
11 | .then(res=>{
12 | dispatch({
13 | type:GET_DEPARTMENTS_SUCCESS,
14 | payload:res
15 | })
16 | return res
17 | })
18 | .catch(error=>{
19 | dispatch({
20 | type:GET_DEPARTMENTS_FAIL,
21 | payload:{error}
22 | })
23 | return error
24 | })
25 | }
26 |
27 | export const GET_DEPARTMENTS_BEGIN = 'GET_DEPARTMENTS_BEGIN'
28 | export const GET_DEPARTMENTS_SUCCESS='GET_DEPARTMENTS_SUCCESS'
29 | export const GET_DEPARTMENTS_FAIL='GET_DEPARTMENTS_FAIL'
--------------------------------------------------------------------------------
/src/redux/reducer/signinReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | POST_SIGNIN_BEGIN,
3 | POST_SIGNIN_SUCCESS,
4 | POST_SIGNIN_FAIL
5 | } from '../action/signinAction'
6 |
7 | const initialState = {
8 | signin_loading: false,
9 | error: {},
10 | }
11 |
12 | export default (state = initialState, action) => {
13 | switch (action.type) {
14 | case POST_SIGNIN_BEGIN:
15 | return {
16 | ...state,
17 | signin_loading: true
18 | }
19 | case POST_SIGNIN_SUCCESS:
20 | return {
21 | ...state,
22 | signin_loading: false,
23 | }
24 | case POST_SIGNIN_FAIL:
25 | return {
26 | ...state,
27 | signin_loading: false,
28 | error: action.payload.error.response.data
29 | }
30 | default:
31 | return state
32 | }
33 | }
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/dashboard.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | .outbox
4 | min-width: 320px
5 | min-height: 568px
6 | max-width: 100vw
7 | max-height: 100vh
8 |
9 | .box
10 | display: flex
11 | justify-content: space-around
12 | margin-top: 12vh
13 | max-width: 100%
14 |
15 | .filter
16 | margin: 1rem
17 |
18 | .products
19 | display: flex
20 | width: 80%
21 | flexWrap: wrap !important
22 |
23 | .product
24 | min-width: 150px
25 | // margin: .5rem
26 |
27 | @media screen and (max-width: $tablet)
28 | .box
29 | display: flex
30 | flex-direction: column
31 | justify-content: space-around
32 | align-items: center
33 | margin-top: 8vh
34 |
35 | .filter
36 | margin: 0
37 | width: 100%
38 |
39 |
40 | .products
41 | width: 100%
--------------------------------------------------------------------------------
/src/redux/action/variantsAction.js:
--------------------------------------------------------------------------------
1 | import serverCall from '../../modules/serverCall'
2 |
3 | export const getVariantsByProductId=(productId)=>dispatch=>{
4 | dispatch({
5 | type:GET_VARIANTS_QUERY_BEGIN,
6 | })
7 | return serverCall({
8 | method:'GET',
9 | url:`/variants?productId=${productId}`
10 | })
11 | .then(res=>{
12 | dispatch({
13 | type: GET_VARIANTS_QUERY_SUCCESS,
14 | payload: res
15 | })
16 | return res
17 | })
18 | .catch(error=>{
19 | dispatch({
20 | type: GET_VARIANTS_QUERY_FAIL,
21 | payload: {error}
22 | })
23 | return error
24 | })
25 | }
26 |
27 | export const GET_VARIANTS_QUERY_BEGIN='GET_VARIANTS_QUERY_BEGIN'
28 | export const GET_VARIANTS_QUERY_SUCCESS='GET_VARIANTS_QUERY_SUCCESS'
29 | export const GET_VARIANTS_QUERY_FAIL='GET_VARIANTS_QUERY_FAIL'
--------------------------------------------------------------------------------
/src/redux/reducer/departmentReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_DEPARTMENTS_BEGIN,
3 | GET_DEPARTMENTS_SUCCESS,
4 | GET_DEPARTMENTS_FAIL
5 | } from '../action/departmentAction'
6 |
7 | const initialState = {
8 | loading: false,
9 | departments: null,
10 | error: null,
11 | }
12 |
13 | export default (state = initialState, action) => {
14 | switch (action.type) {
15 | case GET_DEPARTMENTS_BEGIN:
16 | return {
17 | loading: true,
18 | error: null
19 | }
20 | case GET_DEPARTMENTS_SUCCESS:
21 | return {
22 | loading: false,
23 | departments: action.payload.data.departments
24 | }
25 | case GET_DEPARTMENTS_FAIL:
26 | return {
27 | loading: false,
28 | error: action.payload.error.response.data
29 | }
30 | default:
31 | return state
32 | }
33 | }
--------------------------------------------------------------------------------
/src/redux/action/signinAction.js:
--------------------------------------------------------------------------------
1 | import serverCall from '../../modules/serverCall'
2 |
3 | export const signin=(fullname,email,password,verifyPassword)=>dispatch=>{
4 | dispatch({
5 | type: POST_SIGNIN_BEGIN,
6 | })
7 | return serverCall({
8 | method:'POST',
9 | url:'/users/signin',
10 | data:{
11 | fullname,email,password,verifyPassword
12 | }
13 | })
14 | .then(res=>{
15 | dispatch({
16 | type:POST_SIGNIN_SUCCESS,
17 | payload:res
18 | })
19 | return res
20 | })
21 | .catch(error=>{
22 | dispatch({
23 | type:POST_SIGNIN_FAIL,
24 | payload:{error}
25 | })
26 | throw error
27 | })
28 | }
29 |
30 | export const POST_SIGNIN_BEGIN='POST_SIGNIN_BEGIN'
31 | export const POST_SIGNIN_SUCCESS='POST_SIGNIN_SUCCESS'
32 | export const POST_SIGNIN_FAIL='POST_SIGNIN_FAIL'
33 |
--------------------------------------------------------------------------------
/src/redux/reducer/filterReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | FILTER_BEGIN,
3 | FILTER_SUCCESS,
4 | FILTER_FAIL
5 | } from '../action/filterAction'
6 |
7 | const initialState = {
8 | loading: false,
9 | filter_result: null,
10 | error: null,
11 | }
12 |
13 | export default (state = initialState, action) => {
14 | switch (action.type) {
15 | case FILTER_BEGIN:
16 | return {
17 | ...state,
18 | loading: true,
19 | error: null
20 | }
21 | case FILTER_SUCCESS:
22 | return {
23 | ...state,
24 | loading: false,
25 | filter_result: action.payload.data.filter
26 | }
27 |
28 | case FILTER_FAIL:
29 | return {
30 | ...state,
31 | loading: false,
32 | error: action.payload.error.response.data
33 | }
34 | default:
35 | return state
36 | }
37 | }
--------------------------------------------------------------------------------
/src/modules/mediaQuery/_app.sass:
--------------------------------------------------------------------------------
1 | //breakpoints
2 | $mobileS: 320px;
3 | $mobileM: 375px;//iphone
4 | $mobileL: 425px;//plus 414px
5 | $tablet: 768px;
6 | $laptop: 1024px;
7 | $laptopL: 1440px;
8 | $desktop: 2560px;
9 |
10 | =device($point)
11 | @if $point == mobileS
12 | @media (min-width: $mobileS)
13 | @content
14 | @else if $point == mobileM
15 | @media (min-width: $mobileM)
16 | @content
17 | @else if $point == mobileL
18 | @media (min-width: $mobileL)
19 | @content
20 | @else if $point == tablet
21 | @media (min-width: $tablet)
22 | @content
23 | @else if $point == laptop
24 | @media (min-width: $laptop)
25 | @content
26 | @else if $point == laptopL
27 | @media (min-width: $laptopL)
28 | @content
29 | @else if $point == desktop
30 | @media (min-width: $desktop)
31 | @content
--------------------------------------------------------------------------------
/src/pages/shoppingBag/stylesheets/table.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 |
4 | .outbox
5 | font-size: 1rem
6 | .id
7 | font-size: 0.7em
8 | .pic_outbox
9 | max-width: 6em
10 | padding: 0
11 | display: flex
12 | justify-content: center
13 | align-items: center
14 | img
15 | max-width: calc(100% - 1.3em)
16 |
17 | .title_outbox
18 | display: flex
19 | flex-direction: column
20 | width: max-content
21 | margin: 0
22 |
23 | .title
24 | word-wrap: none !important
25 | min-width: 6em
26 | max-width: 8em
27 | margin-bottom: 1em
28 | .qty_outbox
29 | display: flex
30 | flex-wrap: nowrap
31 |
32 | .btn
33 | padding: 0 0.5em
34 | margin: 0 0.5em
35 |
36 | @media screen and (max-width: $tablet)
37 | .outbox
38 | font-size: .7rem
39 | .pic_outbox
40 | font-size: .6rem
41 | .btn
42 | font-size: .5rem
43 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/Product.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Card } from 'react-bootstrap'
3 | import styles from '../stylesheets/product.module.sass'
4 |
5 | export default function Product({ title, color, price, image }) {
6 | return (
7 |
12 |
13 |
14 | {title}
15 |
16 |
17 | {price}
18 |
19 |
20 |
21 | {color}
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/sub_selection.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | @media screen and (max-width: $tablet)
4 | .outbox
5 | width: 100%
6 | min-height: max-content
7 | padding: 0 2rem
8 |
9 | .box
10 | z-index: 2
11 | width: 100%
12 | height: 100%
13 | display: flex
14 | justify-content: space-around
15 | align-items: center
16 | color: black
17 | background: rgba(125,125,125,.2)
18 | border-radius: 8px
19 | flex-wrap: wrap
20 | .content
21 | height: 3rem
22 | display: flex
23 | align-items: center
24 | font-size: .8rem
25 | cursor: pointer
26 | padding: 2px .5rem
27 | white-space: nowrap
28 |
29 | .selected
30 | border-bottom: 1px solid blue
31 |
32 | @media screen and (max-width: $mobileL)
33 | .content
34 | font-size: .5rem
35 | height: 1rem
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/redux/reducer/variantsReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_VARIANTS_QUERY_BEGIN,
3 | GET_VARIANTS_QUERY_SUCCESS,
4 | GET_VARIANTS_QUERY_FAIL
5 | } from '../action/variantsAction'
6 |
7 | const initialState = {
8 | variants: null,
9 | loading: false,
10 | error: null,
11 | }
12 |
13 | export default (state = initialState, action) => {
14 | switch (action.type) {
15 | case GET_VARIANTS_QUERY_BEGIN:
16 | return {
17 | ...state,
18 | loading: true,
19 | error: null
20 | }
21 | case GET_VARIANTS_QUERY_SUCCESS:
22 | return {
23 | ...state,
24 | loading: false,
25 | variants: action.payload.data.variants
26 | }
27 | case GET_VARIANTS_QUERY_FAIL:
28 | return {
29 | ...state,
30 | loading: false,
31 | error: action.payload.error
32 | }
33 | default:
34 | return state
35 | }
36 | }
--------------------------------------------------------------------------------
/src/pages/checkout/stylesheets/table.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Comfortaa');
2 | @import '../../../modules/mediaQuery/app'
3 |
4 | .outbox
5 | display: flex
6 | flex-direction: column
7 |
8 | .row
9 | display: flex
10 | justify-content: space-between
11 | margin: 3% 0
12 | .pic
13 | max-width: 6em
14 | margin-right: 1em
15 | display: flex
16 | justify-content: center
17 | align-items: center
18 | overflow:
19 | img
20 | max-width: 70%
21 | border-radius: 10%
22 |
23 | .title
24 | margin: 0 .3em
25 | font-size: 1em
26 | font-family: sans-serif
27 |
28 | .price
29 | margin-left: auto
30 | font-size: 1em
31 | font-family: 'Comfortaa', cursive;
32 | font-weight: 600
33 |
34 |
35 | @media screen and (max-width: $tablet)
36 | .outbox
37 | font-size: .7rem
38 |
39 | .pic
40 | font-size: .6rem
41 | .price
42 | font-size: .5rem
--------------------------------------------------------------------------------
/src/components/header/components/Search.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/search.module.sass'
3 | import AutoComplete from '../../autoComplete/AutoCompleteContainer'
4 | import jumpTo from '../../../modules/Navigation'
5 | export default function Search({
6 | search,
7 | onChange,
8 | handleSuggest,
9 | input_value
10 | }) {
11 | return (
12 |
13 | {/* search input */}
14 |
20 |
21 | {
23 | search(input_value).then(res => jumpTo('/dashboard'))
24 | }}
25 | >
26 | Search
27 |
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/filter.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 |
4 | .outbox
5 | width: 250px
6 | min-width: 100px
7 | background: white
8 |
9 | .box
10 | display: flex
11 | flex-direction: column
12 |
13 | .title
14 | font-size: 1.5rem
15 | background: white
16 |
17 | .title_border
18 | border-bottom: 1px solid green
19 | padding-bottom: .5rem
20 |
21 | .tags
22 | display: flex
23 | flex-wrap: wrap
24 |
25 | .tag
26 | padding: .2rem
27 | background: rgba(0,0,0,.1)
28 | margin: .2rem
29 | display: flex
30 | flex-wrap: nowrap
31 |
32 | .tag_content
33 | margin: 0 .2rem
34 |
35 | .tag_close
36 | padding: 0 .2rem
37 | border-left: 1px solid white
38 | cursor: pointer
39 |
40 | .content
41 | display: flex
42 | flex-direction: column
43 | margin-top: 1rem
44 |
45 | .block
46 | margin-bottom: 1rem
47 |
48 | .sub_title
49 | // font-size: 1rem
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce-reactjs",
3 | "version": "1.1.0",
4 | "private": true,
5 | "homepage": "https://zack-ecommerce-reactjs.herokuapp.com/",
6 | "dependencies": {
7 | "axios": "^0.18.0",
8 | "bootstrap": "^4.3.1",
9 | "node-sass": "^4.12.0",
10 | "react": "^16.8.6",
11 | "react-bootstrap": "^1.0.0-beta.8",
12 | "react-dom": "^16.8.6",
13 | "react-redux": "^7.0.2",
14 | "react-responsive": "^6.1.2",
15 | "react-router-dom": "^5.0.0",
16 | "react-scripts": "^3.0.0",
17 | "redux": "^4.0.1",
18 | "redux-thunk": "^2.3.0"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": "react-app"
28 | },
29 | "browserslist": [
30 | ">0.2%",
31 | "not dead",
32 | "not ie <= 11",
33 | "not op_mini all"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/checkbox.module.sass:
--------------------------------------------------------------------------------
1 |
2 | .outbox
3 | display: flex
4 | align-items: center
5 | margin: .5rem 0
6 | label
7 | margin: 0
8 | display: flex
9 | &:hover
10 | cursor: pointer
11 | input
12 | opacity: 0
13 | display: none
14 | &:checked+label
15 | .checkbox
16 | // background: blue
17 | width: 16px
18 | height: 8px
19 | border-width: 0 0 1px 2px;
20 | opacity: 1;
21 | border-style: solid;
22 | border-color: green
23 | transform: rotateZ(-50deg) scale(1);
24 | animation: check .2s ease-out
25 | @keyframes check
26 | 0%
27 | opacity: 1;
28 | transform: rotateZ(-50deg) scale(0);
29 | 100%
30 | opacity: 1;
31 | transform: rotateZ(-50deg) scale(1.8);
32 |
33 | .checkbox
34 | width: 1rem
35 | height: 1rem
36 | border: 1px solid grey
37 | display: flex
38 | justify-content: center
39 | align-items: center
40 | margin-right: .8rem
41 |
42 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import LoginSignin from './LoginSignin'
3 | import {
4 | validateExistence,
5 | validateEmail,
6 | validateLength,
7 | validateLowerCase,
8 | validateUpperCase
9 | } from './utils/validation'
10 |
11 |
12 | const INPUT_CONFIG = [
13 | {
14 | name: "email",
15 | validations: [validateExistence, validateEmail]
16 | },
17 | {
18 | name: "password",
19 | validations: [validateExistence, validateLength(6, 15), validateLowerCase, validateUpperCase]
20 | }
21 | ]
22 |
23 | export default function Login({ postToken, login_loading, login_error }) {
24 | return (
25 |
26 |
35 |
36 | )
37 | }
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/components/header/stylesheets/search.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | .outbox
4 | display: flex
5 | align-items: center
6 | width: 100%
7 | margin: 0 1rem
8 |
9 | .auto
10 | width: max-content
11 |
12 | .btn
13 | margin-left: 1rem
14 | border: 2px solid rgb(94, 114, 100)
15 | border-radius: 5px
16 | background: rgb(65,65,65)
17 | &:hover
18 | background: rgb(94, 114, 100)
19 | button
20 | color: white
21 | button
22 | background: transparent
23 | border: 0
24 | color: white
25 |
26 | @media screen and (max-width: $tablet)
27 | .outbox
28 | display: flex
29 | flex-direction: column
30 | margin: 0
31 | padding: .5rem 0
32 | font-size: .8rem
33 |
34 | .auto
35 | width: 100%
36 | padding: 0 1rem
37 | div
38 | input
39 | width: 100%
40 |
41 | .btn
42 | width: calc( 100% - 2rem)
43 | margin: .5rem 1rem 0 1rem
44 | button
45 | width: 100%
46 | padding: 0
47 | margin: 0
48 | border: none
49 |
50 |
--------------------------------------------------------------------------------
/src/modules/mediaQuery/index.js:
--------------------------------------------------------------------------------
1 | export const size = {
2 | mobileS: '320px',
3 | mobileM: '375px',
4 | mobileL: '425px',
5 | tablet: '768px',
6 | laptop: '1024px',
7 | laptopL: '1440px',
8 | desktop: '2560px'
9 | }
10 | const device = {
11 | min: {
12 | mobileS: `(min-width: ${size.mobileS})`,
13 | mobileM: `(min-width: ${size.mobileM})`,
14 | mobileL: `(min-width: ${size.mobileL})`,
15 | tablet: `(min-width: ${size.tablet})`,
16 | laptop: `(min-width: ${size.laptop})`,
17 | laptopL: `(min-width: ${size.laptopL})`,
18 | desktop: `(min-width: ${size.desktop})`,
19 | desktopL: `(min-width: ${size.desktop})`
20 | },
21 | max: {
22 | mobileS: `(max-width: ${size.mobileS})`,
23 | mobileM: `(max-width: ${size.mobileM})`,
24 | mobileL: `(max-width: ${size.mobileL})`,
25 | tablet: `(max-width: ${size.tablet})`,
26 | laptop: `(max-width: ${size.laptop})`,
27 | laptopL: `(max-width: ${size.laptopL})`,
28 | desktop: `(max-width: ${size.desktop})`,
29 | desktopL: `(max-width: ${size.desktop})`
30 | },
31 | };
32 |
33 | export default device
--------------------------------------------------------------------------------
/src/components/header/components/UserHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/userHeader.module.sass'
3 | import { NavDropdown } from 'react-bootstrap'
4 | import Auth from '../../../modules/Auth'
5 | import jumpTo from '../../../modules/Navigation'
6 |
7 | export default function UserHeader({ user_token }) {
8 | return (
9 |
10 |
jumpTo('/bag')}>
11 | CART
12 |
13 | {(user_token && Object.keys(user_token).length > 0)
14 | ?
15 |
16 |
17 |
18 | logout
19 |
20 |
21 |
22 | :
23 |
24 |
jumpTo('/login')}>
25 | Login
26 |
27 |
28 | }
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/productOverview/stylesheets/variants.module.sass:
--------------------------------------------------------------------------------
1 | @import '~bootstrap/scss/_functions'
2 | @import '~bootstrap/scss/_variables'
3 | @import '~bootstrap/scss/mixins/_breakpoints'
4 | @import url('https://fonts.googleapis.com/css?family=Comfortaa');
5 |
6 | .color_title
7 | color: black
8 | font-weight: bold
9 | font-family: 'Comfortaa', cursive;
10 | margin-bottom: 0.5rem
11 |
12 | .color_name
13 | color: grey
14 | font-family: 'Comfortaa', cursive;
15 |
16 | .sizes
17 | width: 100%
18 | color: black
19 | font-weight: bold
20 | font-family: 'Comfortaa', cursive;
21 |
22 | .size_title
23 | margin-bottom: 0.5rem
24 |
25 | .color_pic
26 | width: 100%
27 | height: 10%
28 | margin-bottom: 1rem
29 | img
30 | width: 10%
31 | margin-right: 0.7rem
32 |
33 | @include media-breakpoint-down(sm)
34 | .color_title
35 | font-size: 0.75rem
36 | margin-bottom: 0.25rem
37 | .color_name
38 | font-size: 0.5rem
39 | .size_title
40 | font-size: 0.75rem
41 | margin-bottom: 0.25rem
42 | .color_pic
43 | margin-bottom: 0.5rem
44 | img
45 | width: 20%
46 |
--------------------------------------------------------------------------------
/src/pages/productOverview/components/Variants.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/variants.module.sass'
3 | import Sizes from '../components/Sizes'
4 |
5 |
6 | export default function Variants({ color, size, variants, selectedSize, handleClick, clickSize }) {
7 | return (
8 |
9 |
10 | COLOUR:
11 |
12 |
13 | {color}
14 |
15 |
16 | {variants && variants.map(v =>
17 |
handleClick(v)} key={v.color} src={v.imagePath} alt="" />
18 | )}
19 |
20 |
21 |
22 | SIZES:
23 |
24 |
25 |
30 |
31 |
32 |
33 | )
34 | }
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Zack Yin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/header/components/Menu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/menu.module.sass'
3 | import DropList from './DropList'
4 | import jumpTo from '../../../modules/Navigation'
5 | export default function Menu({
6 | departments,
7 | getProductsByCategory,
8 | getAllProducts
9 | }) {
10 | return (
11 |
12 | {/* lists */}
13 |
14 | {/* departments */}
15 | {departments && departments.map(d =>
16 |
19 | getProductsByCategory(c)}
21 | department={d.departmentName}
22 | categories={d.categories.split(',')}
23 | />
24 |
25 | )}
26 |
27 | {/* all product */}
28 |
{
30 | getAllProducts()
31 | jumpTo('/dashboard')
32 | }}
33 | >
34 | All Product
35 |
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/src/pages/checkout/components/Subtotal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/subtotal.module.sass'
3 | import calcPrice from '../utils/calcPrice'
4 |
5 | export default function Subtotal({ subtotal }) {
6 | return (
7 |
8 |
9 |
10 |
11 | Subtotal
12 |
13 |
14 | ${subtotal}
15 |
16 |
17 |
18 |
19 | Shipping
20 |
21 |
22 | Free
23 |
24 |
25 |
26 |
27 | Taxes
28 |
29 |
30 | ${calcPrice(subtotal).taxes}
31 |
32 |
33 |
34 |
35 |
36 | Total
37 |
38 |
39 | ${calcPrice(subtotal).total}
40 |
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/autoComplete/auto.module.sass:
--------------------------------------------------------------------------------
1 | .outbox
2 | display: flex
3 | flex-direction: column
4 | align-items: flex-start
5 | // min-width: max-content
6 | z-index: 3
7 | position: relative
8 | input
9 | // border: 1px solid grey
10 | border: none
11 | outline: none
12 | background: transparent
13 | border-bottom: 1px solid black
14 | box-shadow: none
15 | padding: 0
16 | height: 2rem
17 | caret-color: green
18 | cursor: default
19 | input::placeholder
20 | padding-left: .5rem
21 | color: white !important
22 | .sugges_outbox
23 | border: 1px solid grey
24 | min-width: min-content
25 | background: white
26 | position: absolute
27 | top: 2rem
28 | .sugges_box
29 |
30 | .sugges_category
31 | padding-left: 1rem
32 | font-size: 1.1rem
33 | color: white
34 | background: rgb(141, 232, 219)
35 | .sugges_arr
36 | display: flex
37 | flex-direction: column
38 | .sugges_value
39 | font-size: 1rem
40 | color: grey
41 | padding: 0 2rem
42 | white-space: nowrap
43 | width: min-content
44 | min-width: 100%
45 | &:hover
46 | background: rgb(141, 232, 219)
47 | width: 100%
48 | cursor: pointer
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/pages/checkoutSuccess/stylesheet/checkoutSuccess.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Comfortaa');
2 |
3 |
4 | .outbox
5 | width: 100vw
6 | min-height: 100vh
7 | background: rgb(244, 244, 244)
8 |
9 | .box
10 | width: 100%
11 | padding-top: 12vh
12 | display: flex
13 | flex-direction: column
14 | align-items: center
15 |
16 | .checkMark
17 | display: flex
18 | flex-direction: column
19 | align-items: center
20 | .pic
21 | width: 100px
22 | height: 100px
23 | img
24 | width: 100%
25 | height: 100%
26 | .text
27 | font-size: 2rem
28 | font-family: 'Comfortaa', cursive;
29 |
30 |
31 | .content
32 | width: 100%
33 | padding: 3%
34 | background: white
35 |
36 | .order
37 | display: flex
38 | .num
39 | color: blue
40 | font-weight: 500
41 | margin: 1rem
42 | .title
43 | font-weight: 700
44 | margin: 1rem 0
45 |
46 | .btn
47 | display: flex
48 | justify-content: flex-end
49 | font-family: 'Comfortaa', cursive;
50 | button
51 | background: white
52 | border: 2px groove rgba(0,0,255,0.5)
53 | border-radius: 5px
54 | &:hover
55 | background: rgba(0,0,255,0.3)
56 | color: white
--------------------------------------------------------------------------------
/src/pages/loginsignin/components/Base.js:
--------------------------------------------------------------------------------
1 | import styles from '../stylesheets/base.module.sass'
2 | import React from 'react'
3 | import FormInput from './FormInput'
4 | import Button from './Button'
5 | import Footer from './footer'
6 |
7 | export default function Base({
8 | title,
9 | inputs,
10 | onInputBlur,
11 | onInputFocus,
12 | onInputChange,
13 | onSubmit,
14 | errorMsg,
15 | button_title,
16 | footer_content
17 | }) {
18 | return (
19 |
20 |
Zack Market
21 |
{title}
22 |
23 | {
24 | inputs.map(({ name, validations }) =>
25 |
34 | )
35 | }
36 |
37 |
38 |
39 | )
40 | }
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/Signin.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import LoginSignin from './LoginSignin'
3 | import {
4 | validateExistence,
5 | validateEmail,
6 | validateLength,
7 | validateLowerCase,
8 | validateUpperCase
9 | } from './utils/validation'
10 |
11 |
12 | const INPUT_CONFIG = [
13 | {
14 | name: "fullname",
15 | // validations: [validateLength()]
16 | },
17 | {
18 | name: "email",
19 | validations: [validateExistence, validateEmail]
20 | },
21 | {
22 | name: "password",
23 | validations: [validateExistence, validateLength(6, 15), validateLowerCase, validateUpperCase]
24 | },
25 | {
26 | name: "verifyPassword",
27 | validations: [validateExistence, validateLength(6, 15), validateLowerCase, validateUpperCase]
28 | },
29 | ]
30 |
31 |
32 | export default function Signin({signin,signin_loading,signin_error}) {
33 | return (
34 |
35 |
44 |
45 | )
46 | }
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/pages/shoppingBag/stylesheets/shoppingBag.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Comfortaa');
2 | @import '../../../modules/mediaQuery/app'
3 |
4 |
5 | .outbox
6 | min-width: max-content
7 | min-width: 386px
8 | min-height: 100vh
9 | display: flex
10 | justify-content: center
11 | background: rgb(244, 244, 244)
12 | font-size: 1rem
13 |
14 | .box
15 | width: 100%
16 | margin-top: 12vh
17 | display: flex
18 | justify-content: center
19 | align-items: center
20 |
21 |
22 | .content
23 | padding: 3%
24 | margin: 4em
25 | background: white
26 | min-width: max-content
27 | justify-content: center
28 | align-items: center
29 |
30 |
31 | .title
32 | color: blue
33 | font-size: 2rem
34 | font-family: 'Comfortaa', cursive;
35 |
36 |
37 | .table
38 | width: max-content
39 |
40 | .process_box
41 | width: 100%
42 | display: flex
43 | flex-direction: column
44 | align-items: flex-end
45 |
46 | .total
47 | font-size: 20px
48 | font-weight: bold
49 |
50 |
51 |
52 | // @include media-breakpoint-down(md)
53 | // .box
54 | // max-width: 70%
55 | // @include media-breakpoint-down(sm)
56 | // .box
57 | // max-width: 90%
58 | // .title
59 | // font-size: 1.5rem
60 |
--------------------------------------------------------------------------------
/src/redux/reducer/tokenReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | POST_TOKEN_BEGIN,
3 | POST_TOKEN_SUCCESS,
4 | POST_TOKEN_FAIL,
5 | INSERT_TOKEN_SUCCESS,
6 | INSERT_TOKEN_FAIL
7 | } from '../action/tokenAction'
8 |
9 | const initialState = {
10 | user_token: {},
11 | token_loading: false,
12 | error: {},
13 | insert_token_error: false
14 | }
15 |
16 | export default (state = initialState, action) => {
17 | switch (action.type) {
18 | case POST_TOKEN_BEGIN:
19 | return {
20 | ...state,
21 | token_loading: true,
22 | error: {}
23 | }
24 | case POST_TOKEN_SUCCESS:
25 | return {
26 | ...state,
27 | user_token: action.payload.data.user_token,
28 | token_loading: false
29 | }
30 | case POST_TOKEN_FAIL:
31 | return {
32 | ...state,
33 | token_loading: false,
34 | error: action.payload.error.response.data
35 | }
36 | case INSERT_TOKEN_SUCCESS:
37 | return {
38 | ...state,
39 | user_token: action.payload,
40 | insert_token_error: false
41 | }
42 | case INSERT_TOKEN_FAIL:
43 | return {
44 | ...state,
45 | insert_token_error: true
46 | }
47 | default:
48 | return state
49 | }
50 | }
--------------------------------------------------------------------------------
/src/pages/loginsignin/utils/validation.js:
--------------------------------------------------------------------------------
1 | class Validation {
2 | constructor(assertion, rule, errMsg) {
3 | this.assertion = assertion
4 | this.rule = rule
5 | this.errMsg = errMsg
6 | }
7 | check(text) {
8 | return this.rule(text)
9 | }
10 | errMsg() {
11 | return this.errMsg
12 | }
13 | }
14 |
15 | export const validateExistence = new Validation(
16 | 'input should have value',
17 | text => /\S/.test(text),
18 | 'Required'
19 | )
20 | export const validateEmail = new Validation(
21 | 'input should be an email',
22 | text => !!text.match(/[\w-]+@([\w-]+\.)+[\w-]+/i),
23 | 'invalid email'
24 | )
25 | export const validateLength = (min_len, max_len) => {
26 | const regex = new RegExp(`(?=.{${min_len},${max_len}})`, 'g')
27 | return new Validation(
28 | 'input should within certain length',
29 | text => !!text.match(regex),
30 | `password has to be ${min_len}-${max_len} letter`
31 | )
32 | }
33 | export const validateLowerCase = new Validation(
34 | 'input should have an uppercase',
35 | text => !!text.match(/(?=.*[A-Z])/g),
36 | 'at least one upper case'
37 | )
38 | export const validateUpperCase = new Validation(
39 | 'input should have lowercase',
40 | text => !!text.match(/(?=.*[a-z])/g),
41 | 'at least one lower case'
42 | )
--------------------------------------------------------------------------------
/src/pages/loginsignin/components/FormInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from '../stylesheets/formInput.module.sass'
3 | import capitalizeString from '../utils/capitalizeString'
4 |
5 |
6 | export default function FormInput({
7 | name,
8 | validations,
9 | errorMessage,
10 | onBlur,
11 | onFocus,
12 | }) {
13 | return (
14 |
15 |
16 | {capitalizeString(name)}
17 |
18 |
19 | -1 ? 'password' : 'text'}
21 | name={name}
22 | placeholder={name}
23 | onBlur={(e) => onBlur(e, validate(validations, e.target.value))}
24 | onFocus={onFocus}
25 | />
26 |
27 |
28 | {errorMessage}
29 |
30 |
31 | )
32 | }
33 |
34 | const validate = (validations, val) => {
35 | if (validations) {
36 | for (const validation of validations) {
37 | if (!validation.check(val)) {
38 | return {
39 | isValid: false,
40 | errorMsg: validation.errMsg
41 | }
42 | }
43 | }
44 | }
45 | return { isValid: true }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/src/redux/reducer/checkoutReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_CHECKOUT_BEGIN,
3 | GET_CHECKOUT_SUCCESS,
4 | GET_CHECKOUT_FAIL,
5 | GET_PAYMENT_BEGIN,
6 | GET_PAYMENT_SUCCESS,
7 | GET_PAYMENT_FAIL
8 | } from '../action/checkoutAction'
9 |
10 | const initialState = {
11 | loading: false,
12 | approval_url: null,
13 | payment: null,
14 | error: null,
15 | }
16 |
17 | export default (state = initialState, action) => {
18 | switch (action.type) {
19 | case GET_CHECKOUT_BEGIN:
20 | return {
21 | ...state,
22 | loading: true,
23 | error: null
24 | }
25 | case GET_CHECKOUT_SUCCESS:
26 | return {
27 | loading: false,
28 | approval_url: action.payload.data
29 | }
30 | case GET_CHECKOUT_FAIL:
31 | return {
32 | loading: false,
33 | error: action.payload.error.response.data
34 | }
35 | case GET_PAYMENT_BEGIN:
36 | return {
37 | ...state,
38 | loading: true,
39 | error: null
40 | }
41 | case GET_PAYMENT_SUCCESS:
42 | return {
43 | loading: false,
44 | payment: action.payload.data.payment
45 | }
46 | case GET_PAYMENT_FAIL:
47 | return {
48 | loading: false,
49 | error: action.payload.error.response.data
50 | }
51 | default:
52 | return state
53 | }
54 | }
--------------------------------------------------------------------------------
/src/redux/action/tokenAction.js:
--------------------------------------------------------------------------------
1 | import { login } from '../../modules/serverCall'
2 |
3 | export const postToken = (email, password) => dispatch => {
4 | dispatch({
5 | type: POST_TOKEN_BEGIN
6 | })
7 | return login(email, password)
8 | .then(res => {
9 | dispatch({
10 | type: POST_TOKEN_SUCCESS,
11 | payload: res
12 | })
13 | // console.log('tokenAction res');
14 | // console.log(res);
15 | return res
16 | })
17 | .catch(error => {
18 | dispatch({
19 | type: POST_TOKEN_FAIL,
20 | payload: { error }
21 | })
22 | // console.log('tokenAction error');
23 | // console.log(error.response);
24 | throw error
25 | })
26 | }
27 |
28 | export const insertToken = () => dispatch => {
29 | let token
30 | if (localStorage.getItem('auth')) {
31 | token = JSON.parse(localStorage.getItem('auth'))
32 | dispatch({
33 | type: INSERT_TOKEN_SUCCESS,
34 | payload: token
35 | })
36 | } else {
37 | dispatch({
38 | type: INSERT_TOKEN_FAIL
39 | })
40 | }
41 | }
42 |
43 | export const POST_TOKEN_BEGIN = 'POST_TOKEN_BEGIN'
44 | export const POST_TOKEN_SUCCESS = 'POST_TOKEN_SUCCESS'
45 | export const POST_TOKEN_FAIL = 'POST_TOKEN_FAIL'
46 | export const INSERT_TOKEN_SUCCESS = 'INSERT_TOKEN_SUCCESS'
47 | export const INSERT_TOKEN_FAIL = 'INSERT_TOKEN_FAIL'
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/redux/reducer/cartReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | POST_CART_BEGIN,
3 | POST_CART_SUCCESS,
4 | POST_CART_FAIL,
5 | GET_CART_BY_USERID_BEGIN,
6 | GET_CART_BY_USERID_SUCCESS,
7 | GET_CART_BY_USERID_FAIL
8 | } from '../action/cartAction'
9 |
10 | const initialState = {
11 | cart: {},
12 | loading: false,
13 | error: {},
14 | }
15 |
16 | export default (state = initialState, action) => {
17 | switch (action.type) {
18 | case POST_CART_BEGIN:
19 | return {
20 | ...state,
21 | loading: true,
22 | error: {}
23 | }
24 | case POST_CART_SUCCESS:
25 | return {
26 | ...state,
27 | cart: action.payload.data.cart,
28 | loading: false
29 | }
30 | case POST_CART_FAIL:
31 | return {
32 | ...state,
33 | loading: false,
34 | error: action.payload.error.response.data
35 | }
36 | case GET_CART_BY_USERID_BEGIN:
37 | return {
38 | ...state,
39 | loading: true,
40 | error: {}
41 | }
42 | case GET_CART_BY_USERID_SUCCESS:
43 | return {
44 | ...state,
45 | cart: action.payload.data.cart,
46 | loading: false
47 | }
48 | case GET_CART_BY_USERID_FAIL:
49 | return {
50 | ...state,
51 | loading: false,
52 | error: action.payload.error.response.data
53 | }
54 | default:
55 | return state
56 | }
57 | }
--------------------------------------------------------------------------------
/src/pages/shoppingBag/ShoppingBag.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '../../components/header/headerContainer'
3 | import styles from './stylesheets/shoppingBag.module.sass'
4 | import { Button } from 'react-bootstrap'
5 | import Table from './components/Table'
6 | import jumpTo from '../../modules/Navigation'
7 |
8 |
9 | export default function ShoppingBag(props) {
10 | const { totalPrice, items } = props.cart
11 | const { postCart } = props
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | Shopping Bag
19 |
20 |
21 |
postCart(pid, increase, decrease)}
24 | />
25 |
26 |
27 |
28 | Total: ${totalPrice}
29 |
30 |
31 | jumpTo('/checkout')} variant="primary">
32 | Checkout
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/checkout/stylesheets/checkout.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Comfortaa');
2 | @import '../../../modules/mediaQuery/app'
3 |
4 |
5 | .outbox
6 | max-width: 100vw
7 | min-width: 320px
8 | min-height: 100vh
9 | display: flex
10 | justify-content: center
11 | background: rgb(244, 244, 244)
12 | font-size: 1rem
13 |
14 | .box
15 | margin: 12vh 0
16 | display: flex
17 | justify-content: center
18 |
19 | .content
20 | width: 60%
21 | background: white
22 | display: flex
23 | flex-direction: column
24 | padding: 2em
25 | margin: 2em
26 |
27 | .title
28 | color: blue
29 | font-size: 2em
30 | font-family: 'Comfortaa', cursive;
31 | .sub_title
32 | color: black
33 | font-size: .5em
34 | display: block
35 | margin: 1% 0
36 |
37 | .table
38 | border-top: 1px solid black
39 | border-bottom: 1px solid black
40 | padding: 2%
41 | .prices
42 | padding: 2% 0
43 | border-bottom: 1px solid black
44 | .btn
45 | display: flex
46 | justify-content: flex-end
47 | margin: 2% 0
48 | button
49 | background: white
50 | border: 2px groove rgba(0,0,255,0.5)
51 | border-radius: 5px
52 | &:hover
53 | background: rgba(0,0,255,0.3)
54 | a
55 | color: white
56 | a
57 | text-decoration: none
58 | color: black
59 | font-family: 'Comfortaa', cursive;
60 |
61 |
62 | @media screen and (max-width: $tablet)
63 | .outbox
64 | font-size: .7rem
65 |
66 | .content
67 | width: 80%
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/assets/images/loading-bars.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/assets/images/filter.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/redux/action/cartAction.js:
--------------------------------------------------------------------------------
1 | import serverCall from '../../modules/serverCall'
2 | import Auth from '../../modules/Auth';
3 |
4 |
5 | export const getCartByUserId = () => dispatch => {
6 | let userId = Auth.getUserId()
7 | dispatch({
8 | type: GET_CART_BY_USERID_BEGIN
9 | })
10 | return serverCall({
11 | method: 'GET',
12 | url: `users/${userId}/cart`
13 | })
14 | .then(res => {
15 | dispatch({
16 | type: GET_CART_BY_USERID_SUCCESS,
17 | payload: res
18 | })
19 | return res
20 | })
21 | .catch(error => {
22 | dispatch({
23 | type: GET_CART_BY_USERID_FAIL,
24 | payload: { error }
25 | })
26 | return error
27 | })
28 | }
29 |
30 | export const postCart = (productId, increase, decrease) => (dispatch) => {
31 | let userId = Auth.getUserId()
32 | dispatch({
33 | type: POST_CART_BEGIN
34 | })
35 | return serverCall({
36 | method: 'POST',
37 | url: `users/${userId}/cart`,
38 | data: {
39 | userId,
40 | productId,
41 | increase,
42 | decrease
43 | }
44 | })
45 | .then(res => {
46 | dispatch({
47 | type: POST_CART_SUCCESS,
48 | payload: res
49 | })
50 | return res
51 | })
52 | .catch(error => {
53 | dispatch({
54 | type: POST_CART_FAIL,
55 | payload: { error }
56 | })
57 | return error
58 | })
59 | }
60 |
61 | export const POST_CART_BEGIN = 'POST_CART_BEGIN'
62 | export const POST_CART_SUCCESS = 'POST_CART_SUCCESS'
63 | export const POST_CART_FAIL = 'POST_CART_FAIL'
64 |
65 | export const GET_CART_BY_USERID_BEGIN = 'GET_CART_BY_USERID_BEGIN'
66 | export const GET_CART_BY_USERID_SUCCESS = 'GET_CART_BY_USERID_SUCCESS'
67 | export const GET_CART_BY_USERID_FAIL = 'GET_CART_BY_USERID_FAIL'
--------------------------------------------------------------------------------
/src/pages/dashboard/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import HeaderContainer from '../../components/header/headerContainer'
3 | import Product from './components/Product'
4 | import LoadingAnimation from '../../components/loadingAnimation'
5 | import Filter from './components/Filter'
6 | import styles from './stylesheets/dashboard.module.sass'
7 |
8 |
9 | export default class Dashboard extends Component {
10 | constructor(props) {
11 | super(props);
12 | }
13 | componentDidMount() {
14 | if (!this.props.products) {
15 | this.props.getAllProducts()
16 | }
17 | }
18 | render() {
19 | const { products, applyFilters } = this.props
20 | return (
21 |
22 | {/* Header */}
23 |
24 |
25 | {/* loading animation */}
26 | {this.props.loading &&
27 |
28 | }
29 | {/* filter */}
30 |
31 |
34 |
35 | {/* products */}
36 |
37 | {products && products.map(p =>
38 |
this.props.history.push(`/product-overview/${p._id}`)}>
42 |
48 |
49 | )}
50 |
51 |
52 |
53 | )
54 | }
55 | }
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/modules/serverCall/index.js:
--------------------------------------------------------------------------------
1 | import Auth from '../Auth'
2 | import jumpTo from '../Navigation'
3 | import axios from 'axios'
4 | import qs from 'qs'
5 | import paypalConfig from '../../configs/paypalConfig'
6 |
7 | const URL = 'https://zack-ecommerce-nodejs.herokuapp.com'
8 | // const URL = 'http://localhost:4000'
9 |
10 | const serverCall = (config) => {
11 | //header authorization
12 | if (Auth.user_token) {
13 | const token = Auth.getToken()
14 | config.headers = {
15 | "authorization": token
16 | }
17 | }
18 | //interceptors handle network error
19 | axios.interceptors.response.use(
20 | (response) => {
21 | return response;
22 | },
23 | function (error) {
24 | if (!error.response) {
25 | error.response = {
26 | data: 'net work error',
27 | status: 500
28 | }
29 | }
30 | if(error.response.status===401){
31 | Auth.logout()
32 | jumpTo('/login')
33 | throw error
34 | }
35 | return Promise.reject(error);
36 | });
37 | config.baseURL = URL
38 | return axios(config)
39 | }
40 | export default serverCall
41 |
42 | export const login = (email, password) => {
43 | const body =
44 | {
45 | "credential": {
46 | "email": email,
47 | "password": password
48 | }
49 | }
50 | return serverCall({
51 | method: 'POST',
52 | url: '/users/login',
53 | data: body
54 | })
55 | .then(res => {
56 | Auth.setUserToken(res.data.user_token)
57 | return res
58 | })
59 | }
60 |
61 | export const getPaypalToken = () => {
62 | return axios({
63 | method: 'POST',
64 | url: 'https://api.sandbox.paypal.com/v1/oauth2/token',
65 | headers: { 'content-type': 'application/x-www-form-urlencoded' },
66 | auth: {
67 | username: paypalConfig.username,
68 | password: paypalConfig.password
69 | },
70 | data: qs.stringify({ "grant_type": "client_credentials" })
71 | })
72 | }
--------------------------------------------------------------------------------
/src/redux/action/checkoutAction.js:
--------------------------------------------------------------------------------
1 | import serverCall, { getPaypalToken } from '../../modules/serverCall'
2 |
3 | export const getCheckoutUrl = (cartId) => dispatch => {
4 | dispatch({
5 | type: GET_CHECKOUT_BEGIN,
6 | })
7 | return serverCall({
8 | method: 'GET',
9 | url: `/checkout/${cartId}`
10 | })
11 | .then(res => {
12 | dispatch({
13 | type: GET_CHECKOUT_SUCCESS,
14 | payload: res
15 | })
16 | return res
17 | })
18 | .catch(error => {
19 | dispatch({
20 | type: GET_CHECKOUT_FAIL,
21 | payload: { error }
22 | })
23 | return error
24 | })
25 | }
26 |
27 | export const getPayment = (paymentId, PayerID) => dispatch => {
28 | dispatch({
29 | type: GET_PAYMENT_BEGIN,
30 | })
31 | return getPaypalToken()
32 | .then(response => {
33 | return serverCall({
34 | method: 'GET',
35 | url: `/payment/success?paymentId=${paymentId}&PayerID=${PayerID}`,
36 | headers: { 'Authorization': `Bearer ${response.data.access_token}` }
37 | })
38 | .then(res => {
39 | dispatch({
40 | type: GET_PAYMENT_SUCCESS,
41 | payload: res
42 | })
43 | return res
44 | })
45 | .catch(error => {
46 | dispatch({
47 | type: GET_PAYMENT_FAIL,
48 | payload: { error }
49 | })
50 | return error
51 | })
52 | })
53 | .catch(error => {
54 | dispatch({
55 | type: GET_PAYMENT_FAIL,
56 | payload: { error }
57 | })
58 | return error
59 | })
60 | }
61 |
62 |
63 | export const GET_PAYMENT_BEGIN = 'GET_PAYMENT_BEGIN'
64 | export const GET_PAYMENT_SUCCESS = 'GET_PAYMENT_SUCCESS'
65 | export const GET_PAYMENT_FAIL = 'GET_PAYMENT_FAIL'
66 |
67 | export const GET_CHECKOUT_BEGIN = 'GET_CHECKOUT_BEGIN'
68 | export const GET_CHECKOUT_SUCCESS = 'GET_CHECKOUT_SUCCESS'
69 | export const GET_CHECKOUT_FAIL = 'GET_CHECKOUT_FAIL'
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
25 |
31 | React App
32 |
33 |
34 | You need to enable JavaScript to run this app.
35 |
36 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/pages/productOverview/stylesheets/productOverview.module.sass:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Comfortaa');
2 | @import '~bootstrap/scss/_functions'
3 | @import '~bootstrap/scss/_variables'
4 | @import '~bootstrap/scss/mixins/_breakpoints'
5 |
6 | .outbox
7 | min-width: 320px
8 | min-height: 568px
9 | width: 100vw
10 | height: 100vh
11 | display: flex
12 | flex-direction: column
13 | justify-content: center
14 | align-items: center
15 | background: rgb(244, 244, 244)
16 |
17 | .content_box
18 | width: 70%
19 | height: 80%
20 | display: flex
21 | align-items: center
22 | margin-top: 12vh
23 |
24 | .content
25 | width: 100%
26 | height: 100%
27 | display: flex
28 |
29 | .image
30 | max-width: 50%
31 | // max-height: 100%
32 | overflow: hidden
33 | img
34 | width: 100%
35 | object-fit: cover
36 |
37 | // height: 100%
38 |
39 | .context_outbox
40 | width: 50%
41 | max-height: 100%
42 | margin-left: 3%
43 |
44 | .context
45 | display: flex
46 | flex-direction: column
47 | justify-content: flex-start
48 | background: white
49 | padding: 10px
50 |
51 | .title
52 | color: blue
53 | font-size: 1.5rem
54 | font-family: 'Comfortaa', cursive;
55 |
56 | .description
57 | color: grey
58 | font-size: 1rem
59 |
60 | .price
61 | color: blue
62 | font-size: 1.5rem
63 | font-family: 'Comfortaa', cursive;
64 |
65 | .border
66 | border-bottom: 2px dotted grey
67 | margin: 20px -10px
68 |
69 | .btns
70 | width: 100%
71 | margin: 1rem 0
72 | display: flex
73 | justify-content: space-between
74 | button
75 | width: 45%
76 |
77 | @include media-breakpoint-down(lg)
78 | .content_box
79 | width: 90%
80 |
81 | @include media-breakpoint-down(sm)
82 | .title
83 | font-size: 1rem
84 | .description
85 | font-size: 0.7rem
86 | .price
87 | font-size: 1rem
88 | .border
89 | margin: 0.5rem -0.5rem
90 | .btns
91 | .btn
92 | font-size: 0.75rem
93 | width: 47%
94 | padding: 0.25rem 0
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Full stack ecommerce online store application
2 |
3 | #### back-end code [click here](https://github.com/levelopers/Ecommerce-Nodejs)
4 |
5 | ## General Info
6 |
7 | front-end: Reactjs, Redux, Axios, Sass, react-bootstrap
8 |
9 | back-end: Node.js, Express, Restful API, paypal-sdk, mongodb, mongoose, jwt(jsonwebtoken), heroku, firebase, swaggerHub
10 |
11 | ## snapshots
12 |
13 |
14 |
15 | dashboard
16 |
17 |
18 | 
19 | - - - - -
20 | side menu | dashboard
21 | :-------------------------:|:-------------------------:
22 |  | 
23 |
24 |
25 |
26 |
27 | product-overview
28 |
29 |
30 | 
31 |
32 |
33 |
34 |
35 | checkout
36 |
37 |
38 | 
39 |
40 |
41 |
42 |
43 |
44 |
45 | checkout confirm
46 |
47 |
48 | 
49 |
50 |
51 |
52 |
53 |
54 |
55 | checkout success
56 |
57 |
58 | 
59 |
60 |
61 |
62 |
63 |
64 |
65 | login
66 |
67 |
68 | 
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/src/pages/checkout/Checkout.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from './stylesheets/checkout.module.sass'
3 | import CheckoutTable from './components/CheckoutTable'
4 | import Subtotal from './components/Subtotal'
5 | import Header from '../../components/header/headerContainer'
6 | export default class Checkout extends Component {
7 | constructor(props) {
8 | super(props)
9 |
10 | }
11 | componentDidMount() {
12 | if (Object.keys(this.props.cart).length < 1) {
13 | this.props.getCartByUserId()
14 | } else {
15 | this.props.getCheckoutUrl(this.props.cart._id)
16 | }
17 | }
18 | componentDidUpdate() {
19 | if (!this.props.url && Object.keys(this.props.cart).length > 1) {
20 | this.props.getCheckoutUrl(this.props.cart._id)
21 | }
22 | }
23 | render() {
24 | return (
25 |
26 |
27 |
28 |
29 | {/* title */}
30 |
31 | Checkout
32 |
33 | Hi {this.props.name} Please review your items and press the confirm checkout button. You will enter your address information while your paying on PayPal
34 |
35 |
36 | {/* table */}
37 | {Object.keys(this.props.cart).length > 0 &&
38 |
39 |
40 |
43 |
44 | {/* prices */}
45 |
46 |
49 |
50 |
51 | }
52 | {/* button */}
53 |
56 |
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
3 | import { connect } from 'react-redux'
4 | import { registerNav } from './modules/Navigation'
5 | import { insertToken } from './redux/action/tokenAction'
6 | import LoginContainer from './pages/loginsignin/LoginContainer'
7 | import SigninContainer from './pages/loginsignin/SigninContainer'
8 | import DashboardContainer from './pages/dashboard/DashboardContainer'
9 | import ProductOverview from './pages/productOverview/ProductOverviewContainer'
10 | import ShoppingBagContainer from './pages/shoppingBag/ShoppingBagContainer'
11 | import CheckoutContainer from './pages/checkout/checkoutContainer'
12 | import CheckoutSuccessContainer from './pages/checkoutSuccess/CheckoutSuccessContainer'
13 | import CheckoutCancel from './pages/checkoutCancel/CheckoutCancel'
14 |
15 | class App extends Component {
16 | componentDidMount() {
17 | this.props.insertToken()
18 | }
19 | render() {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | ,
27 | {this.props.token && [
28 | ,
29 | ,
30 | ,
31 | ,
32 | ]}
33 | ,
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 | const mapStoreToProps = state => ({
43 | token: state.token.user_token
44 | })
45 | const mapDispatchToProps = {
46 | insertToken
47 | }
48 | export default connect(mapStoreToProps, mapDispatchToProps)(App);
49 |
--------------------------------------------------------------------------------
/src/components/autoComplete/AutoComplete.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from './auto.module.sass'
3 | export default class AutoComplete extends Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {
7 | isFocus: false,
8 | val: ''
9 | }
10 | }
11 | handleChange = (e) => {
12 | const input = e.target.value
13 | this.props.filter(input)
14 | if(this.props.onChange){
15 | this.props.onChange(input)
16 | }
17 | this.setState({
18 | val: input
19 | })
20 | }
21 | handleFocus = () => {
22 | this.setState({
23 | isFocus: true
24 | })
25 | }
26 | handleBlur = () => {
27 | let blurTimer=null
28 | blurTimer=setTimeout(() => {
29 | this.setState({
30 | isFocus: false
31 | },()=>clearTimeout(blurTimer))
32 | }, 100);
33 | }
34 | handleClick = (v) => {
35 | if(this.props.suggest_value){
36 | this.props.suggest_value(v)
37 | }
38 | this.setState({
39 | val: v
40 | })
41 | }
42 | render() {
43 | const r = this.props.filter_result
44 | return (
45 |
46 |
54 |
55 | {
56 | this.state.isFocus &&
57 | !this.props.error && !this.props.loading &&
58 | Object.keys(r || {}).map(f =>
59 |
60 |
61 | {f}
62 |
63 |
64 | {r[f].map(s =>
65 |
this.handleClick(s)}
69 | >
70 | {s}
71 |
72 | )}
73 |
74 |
75 | )
76 | }
77 |
78 |
79 | )
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/pages/shoppingBag/components/Table.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Table } from 'react-bootstrap'
3 | import styles from '../stylesheets/table.module.sass'
4 | import { Button } from 'react-bootstrap'
5 |
6 | export default function BagTable({ items, handleClick }) {
7 | return (
8 |
9 |
10 |
11 |
12 | {/* id */}
13 | Photos
14 | Title
15 | Qty
16 | Price
17 |
18 |
19 |
20 | {Object.keys(items).map(id =>
21 | // table row
22 |
23 | {/* pic */}
24 |
25 |
26 |
27 |
28 |
29 | {/* title */}
30 |
31 |
32 |
33 | {items[id].item.title}
34 |
35 |
36 | id:
37 |
38 |
39 | {items[id].item._id}
40 |
41 |
42 |
43 | {/* qty */}
44 |
45 |
46 | handleClick(id, true, false)}
50 | >
51 | +
52 |
53 | {items[id].qty}
54 | handleClick(id, false, true)}
58 | >
59 | -
60 |
61 |
62 |
63 | {/* price */}
64 | {items[id].price}
65 |
66 | )}
67 |
68 |
69 |
70 | )
71 | }
72 |
--------------------------------------------------------------------------------
/src/pages/checkoutSuccess/CheckoutSuccess.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import mapSearchURL from './utils/mapSearchURL'
3 | import jumpTo from '../../modules/Navigation'
4 | import Header from '../../components/header/headerContainer'
5 | import checkMark from '../../assets/images/checkmark.svg'
6 | import styles from './stylesheet/checkoutSuccess.module.sass'
7 | export default class CheckoutSuccess extends Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | }
12 | componentDidMount() {
13 | const UrlQuery = this.props.location.search
14 | const queryPair = mapSearchURL(UrlQuery)
15 | if (queryPair.has("paymentId") && queryPair.has("PayerID")) {
16 | this.props.getPayment(queryPair.get("paymentId"), queryPair.get("PayerID"))
17 | }
18 | }
19 |
20 | render() {
21 | const payment = this.props.payment
22 | return (
23 |
24 |
25 | {payment &&
26 |
27 |
28 |
29 |
30 |
31 |
32 | Thank you for your pruchase!
33 |
34 |
35 |
36 |
37 |
38 |
39 | Your order number is:
40 |
41 | {payment.cart}
42 |
43 |
44 |
45 |
46 | Billing & Shipping information:
47 |
48 | {Object.keys(payment.payer.payer_info.shipping_address).map(p =>
49 |
50 | {p}: {payment.payer.payer_info.shipping_address[p]}
51 |
52 | )}
53 |
54 |
55 |
56 | jumpTo('/dashboard')}> Continue Shopping
57 |
58 |
59 |
60 | }
61 |
62 | )
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/pages/dashboard/utils/generateFilterString.js:
--------------------------------------------------------------------------------
1 | /**
2 | * {
3 | * order:[],
4 | * price:[],
5 | * department:[]
6 | * }
7 | */
8 | export default (state) => {
9 | let result = ''
10 | let order = ''
11 | let department = ''
12 | let range = ''
13 | for (let name in state) {
14 | // order filter
15 | if (name === 'order') {
16 | let temp = ''
17 | for (const v of state[name]) {
18 | temp = v
19 | }
20 | if (temp.toUpperCase() === 'ASCENDING') {
21 | order = 'order=price'
22 | }
23 | if (temp.toUpperCase() === 'DESCENDING') {
24 | order = 'order=-price'
25 | }
26 | }
27 | // department filter
28 | if (name === 'department') {
29 | let _base_str = ''
30 | if (state[name].length > 1) {
31 | _base_str = '&department='
32 | } else {
33 | _base_str = 'department='
34 | }
35 | for (let d of state[name]) {
36 | department += _base_str + d.charAt(0).concat(d.slice(1).toLowerCase())
37 | }
38 | }
39 | // price filter
40 | if (name === 'price') {
41 | let _price_str_arr = []
42 | for (let p of state[name]) {
43 | if (p.match(/less/i)) {
44 | p = p.replace(/[\D]+/i, '0 - ')
45 | }
46 | if (p.match(/greater/i)) {
47 | p = p.replace(/[\D]+/i, '').concat(' - 999')
48 | }
49 | _price_str_arr = _price_str_arr.concat(p.match(/[\d]+/g))
50 | }
51 | // sort the array
52 | let sorted_matched_arr = _price_str_arr.sort(function (a, b) { return a - b })
53 | // remove duplicates
54 | for (let index = 0; index < sorted_matched_arr.length; index++) {
55 | if (sorted_matched_arr[index] === sorted_matched_arr[index + 1]) {
56 | sorted_matched_arr.splice(index, 2)
57 | index--
58 | }
59 | }
60 | // generate price range string
61 | for (let index = 0; index < sorted_matched_arr.length; index++) {
62 | if (index % 2 === 0) {
63 | if (range) {
64 | range += `&range=${sorted_matched_arr[index]}-${sorted_matched_arr[index + 1]}`
65 | } else {
66 | range += `range=${sorted_matched_arr[index]}-${sorted_matched_arr[index + 1]}`
67 | }
68 | }
69 | }
70 | }
71 | }
72 | if (range) {
73 | result += range
74 | }
75 | if (order) {
76 | if (result) {
77 | order = `&${order}`
78 | }
79 | result += order
80 | }
81 | if (department) {
82 | if (result) {
83 | department = `&${department}`
84 | }
85 | result += department
86 | }
87 | // console.log(result);
88 | return result
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/src/pages/dashboard/stylesheets/filter_md.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | @media screen and (max-width: $tablet)
4 | .outbox
5 | width: 100vw
6 |
7 | .panels
8 | margin-left: 1rem
9 | margin-top: .2rem
10 | display: flex
11 |
12 | .panel
13 | background: grey
14 | margin-right: 1rem
15 | border-radius: 8px
16 | padding: .3rem
17 | color: white
18 | cursor: pointer
19 |
20 | .content
21 | width: 100%
22 | display: none
23 | margin-top: .5rem
24 | overflow: hidden
25 | transform: translateX(-100vw)
26 |
27 | .show_content
28 | display: block
29 | transform: translateX(0)
30 | transition: -webkit-transform 1s linear
31 | -webkit-transform: translateX(0)
32 | -webkit-transition: transform 1s linear
33 | -moz-transition: transform 1s linear
34 | -o-transition: transform 1s linear
35 |
36 | .filter_box
37 | width: 100%
38 | height: 5vh
39 | display: flex
40 | align-items: center
41 | background: rgba(125,125,125,.2)
42 | position: relative
43 | margin-bottom: 5vh
44 |
45 | .order_box
46 | width: 100%
47 | height: 5vh
48 | display: flex
49 | align-items: center
50 | margin-bottom: .2rem
51 | background: rgba(125,125,125,.2)
52 |
53 | .head_title
54 | max-width: 10rem
55 | margin: 0 1rem
56 | display: flex
57 | img
58 | width: 20px
59 | height: 20px
60 | background: blue
61 | margin-right: .5rem
62 | .head_title_text
63 | font-size: 1rem
64 |
65 | .box
66 | min-width: calc( 100% - 10rem)
67 | height: 100%
68 | display: flex
69 | justify-content: space-around
70 | align-items: center
71 |
72 | .selection
73 | display: flex
74 | flex-direction: column
75 | height: 100%
76 | .sub_selection
77 | height: 0
78 | width: 0
79 | display: none
80 | // width: 100vw
81 | // height: 5vh
82 | position: absolute
83 | top: 100%
84 | left: 0
85 | padding-top: .2rem
86 | overflow: hidden
87 | flex-wrap: wrap
88 | transition: height .2s linear
89 | &:hover, &:focus, &:active
90 | width: 100vw
91 | min-height: 5vh
92 | height: max-content
93 | display: block
94 | &:hover, &:focus, &:active
95 | .sub_selection
96 | width: 100vw
97 | min-height: 5vh
98 | height: max-content
99 | display: block
100 |
101 | @media screen and (max-width: $mobileL)
102 | .head_title
103 | .head_title_text
104 | font-size: .8rem
105 |
106 | .filter_box
107 | margin-bottom: 6vh
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/components/header/stylesheets/header.module.sass:
--------------------------------------------------------------------------------
1 | @import '../../../modules/mediaQuery/app'
2 |
3 | .outbox
4 | display: flex
5 | flex-direction: column
6 | min-width: 320px
7 | width: auto
8 | position: fixed
9 | top: 0
10 | right: 0
11 | left: 0
12 | z-index: 3
13 | background: rgb(125,125,125)
14 |
15 | .content
16 | display: flex
17 | height: 8vh
18 |
19 | .left
20 | display: flex
21 |
22 | .logo
23 | width: 120px
24 | height: 100%
25 | display: flex
26 | justify-content: center
27 | align-items: center
28 | background: pink
29 | margin: 0 1rem
30 | font-size: 1.5rem
31 | &:hover, &:focus
32 | cursor: pointer
33 |
34 | .mid
35 | display: flex
36 | margin: auto
37 | font-size: 1.1rem
38 | color: white !important
39 |
40 | .right
41 | display: flex
42 | align-items: center
43 | margin: 0 1rem
44 |
45 |
46 | @media screen and (max-width: $tablet)
47 | .content
48 | display: flex
49 | justify-content: space-between
50 | align-items: center
51 |
52 | .logo
53 | margin-left: auto
54 | margin-right: 2rem
55 | font-size: 1.3rem
56 |
57 | .toggle_outbox
58 | display: flex
59 | width: 3rem
60 | flex-wrap: nowrap
61 | position: absolute
62 | top: 0
63 | left: 0
64 | right: 0
65 | bottom: 0
66 |
67 | .toggle_icon
68 | height: max-content
69 | margin: .6rem
70 | &:hover, &:blur
71 | .bar1
72 | transform: rotate(-45deg) translate(-9px, 6px)
73 | .bar2
74 | opacity: 0
75 | .bar3
76 | transform: rotate(45deg) translate(-8px, -8px)
77 |
78 | .bar1, .bar2, .bar3
79 | width: 2rem
80 | height: 0.3rem
81 | background-color: #333
82 | margin: 6px 0
83 | transition: 0.4s
84 |
85 | .toggle_content
86 | width: 80vw
87 | min-width: 120px
88 | height: 100vh
89 | background: rgb(125,125,125)
90 | color: white
91 | z-index: 1000
92 | display: flex
93 | flex-direction: column
94 |
95 | .side_title
96 | width: 100%
97 | background: rgb(65,65,65)
98 | font-size: 1rem
99 | padding: .2rem .2rem .2rem .5rem
100 | display: flex
101 | align-items: center
102 | flex-wrap: nowrap
103 |
104 | .side_title_close
105 | font-size: 1.5rem
106 | margin-left: auto
107 | margin-right: 1rem
108 | &:hover
109 | cursor: pointer
110 |
111 | .side_content
112 | padding: 0.8rem
113 | font-size: .8rem
114 | white-space: nowrap
115 | display: flex
116 | align-items: center
117 | border-bottom: 1px solid rgba(0,0,0,.5)
118 | &:hover
119 | cursor: pointer
120 | color: #0056b3
121 |
122 | #toggle
123 | background-color: #FFE600
124 | position: fixed
125 | top: 0
126 | left: 0
127 | overflow: scroll
128 |
129 | .hide
130 | transform: translate3d(-80vw, 0, 0)
131 | transition: transform .5s linear
132 |
133 | .show
134 | transform: translate3d(0vw, 0, 0)
135 | transition: transform .5s linear
136 | + .toggle_icon
137 | display: none
138 |
139 |
--------------------------------------------------------------------------------
/src/redux/reducer/productReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_ALL_PRODUCTS_BEGIN,
3 | GET_ALL_PRODUCTS_SUCCESS,
4 | GET_ALL_PRODUCTS_FAIL,
5 | GET_PRODUCT_BEGIN,
6 | GET_PRODUCT_SUCCESS,
7 | GET_PRODUCT_FAIL,
8 | GET_PRODUCTS_BY_CATEGORY_BEGIN,
9 | GET_PRODUCTS_BY_CATEGORY_SUCCESS,
10 | GET_PRODUCTS_BY_CATEGORY_FAIL,
11 | SEARCH_BEGIN,
12 | SEARCH_SUCCESS,
13 | SEARCH_FAIL,
14 | APPLY_FILTERS_BEGIN,
15 | APPLY_FILTERS_SUCCESS,
16 | APPLY_FILTERS_FAIL,
17 | } from '../action/productAction'
18 |
19 | const initialState = {
20 | products: null,
21 | product: null,
22 | loading: false,
23 | error: null,
24 | }
25 |
26 | export default (state = initialState, action) => {
27 | switch (action.type) {
28 | case GET_ALL_PRODUCTS_BEGIN:
29 | return {
30 | ...state,
31 | loading: true,
32 | error: null
33 | }
34 | case GET_ALL_PRODUCTS_SUCCESS:
35 | return {
36 | ...state,
37 | loading: false,
38 | products: action.payload.data.products
39 | }
40 | case GET_ALL_PRODUCTS_FAIL:
41 | return {
42 | ...state,
43 | loading: false,
44 | error: action.payload.error.response.data
45 | }
46 | case GET_PRODUCT_BEGIN:
47 | return {
48 | ...state,
49 | loading: true,
50 | error: null
51 | }
52 | case GET_PRODUCT_SUCCESS:
53 | return {
54 | ...state,
55 | loading: false,
56 | product: action.payload.data.product
57 | }
58 | case GET_PRODUCT_FAIL:
59 | return {
60 | ...state,
61 | loading: false,
62 | error: action.payload.error.response.data
63 | }
64 | case GET_PRODUCTS_BY_CATEGORY_BEGIN:
65 | return {
66 | ...state,
67 | loading: true,
68 | error: null
69 | }
70 | case GET_PRODUCTS_BY_CATEGORY_SUCCESS:
71 | return {
72 | ...state,
73 | loading: false,
74 | products: action.payload.data.products
75 | }
76 | case GET_PRODUCTS_BY_CATEGORY_FAIL:
77 | return {
78 | ...state,
79 | loading: false,
80 | error: action.payload.error.response.data
81 | }
82 | case SEARCH_BEGIN:
83 | return {
84 | ...state,
85 | loading: true,
86 | error: null
87 | }
88 | case SEARCH_SUCCESS:
89 | return {
90 | ...state,
91 | loading: false,
92 | products: action.payload.data.products
93 | }
94 | case SEARCH_FAIL:
95 | return {
96 | ...state,
97 | loading: false,
98 | error: action.payload.error.response.data
99 | }
100 | case APPLY_FILTERS_BEGIN:
101 | return {
102 | ...state,
103 | loading: true,
104 | error: null
105 | }
106 | case APPLY_FILTERS_SUCCESS:
107 | return {
108 | ...state,
109 | loading: false,
110 | products: action.payload.data.products
111 | }
112 | case APPLY_FILTERS_FAIL:
113 | return {
114 | ...state,
115 | loading: false,
116 | error: action.payload.error.response.data
117 | }
118 | default:
119 | return state
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/redux/action/productAction.js:
--------------------------------------------------------------------------------
1 | import serverCall from '../../modules/serverCall'
2 |
3 | export const getAllProducts=()=>dispatch=>{
4 | dispatch({
5 | type:GET_ALL_PRODUCTS_BEGIN,
6 | })
7 | return serverCall({
8 | method:'GET',
9 | url:`/products`
10 | })
11 | .then(res=>{
12 | dispatch({
13 | type: GET_ALL_PRODUCTS_SUCCESS,
14 | payload: res
15 | })
16 | return res
17 | })
18 | .catch(error=>{
19 | dispatch({
20 | type: GET_ALL_PRODUCTS_FAIL,
21 | payload: {error}
22 | })
23 | return error
24 | })
25 | }
26 |
27 | export const getProduct=(id)=>dispatch=>{
28 | dispatch({
29 | type:GET_PRODUCT_BEGIN,
30 | })
31 | return serverCall({
32 | method:'GET',
33 | url:`/products/${id}`
34 | })
35 | .then(res=>{
36 | dispatch({
37 | type: GET_PRODUCT_SUCCESS,
38 | payload: res
39 | })
40 | return res
41 | })
42 | .catch(error=>{
43 | dispatch({
44 | type: GET_PRODUCT_FAIL,
45 | payload: {error}
46 | })
47 | return error
48 | })
49 | }
50 |
51 | export const getProductsByCategory=(c)=>dispatch=>{
52 | dispatch({
53 | type:GET_PRODUCTS_BY_CATEGORY_BEGIN,
54 | })
55 | return serverCall({
56 | method:'GET',
57 | url:`/products?category=${c}`
58 | })
59 | .then(res=>{
60 | dispatch({
61 | type: GET_PRODUCTS_BY_CATEGORY_SUCCESS,
62 | payload: res
63 | })
64 | return res
65 | })
66 | .catch(error=>{
67 | dispatch({
68 | type: GET_PRODUCTS_BY_CATEGORY_FAIL,
69 | payload: {error}
70 | })
71 | return error
72 | })
73 | }
74 |
75 | export const search=(text)=>dispatch=>{
76 | dispatch({
77 | type:SEARCH_BEGIN,
78 | })
79 | return serverCall({
80 | method:'GET',
81 | url:`/search?query=${text}`
82 | })
83 | .then(res=>{
84 | dispatch({
85 | type: SEARCH_SUCCESS,
86 | payload: res
87 | })
88 | return res
89 | })
90 | .catch(error=>{
91 | dispatch({
92 | type: SEARCH_FAIL,
93 | payload: {error}
94 | })
95 | return error
96 | })
97 | }
98 |
99 | export const applyFilters=(filter_string)=>dispatch=>{
100 | dispatch({
101 | type:APPLY_FILTERS_BEGIN,
102 | })
103 | return serverCall({
104 | method:'GET',
105 | url:`/products?${filter_string}`
106 | })
107 | .then(res=>{
108 | dispatch({
109 | type: APPLY_FILTERS_SUCCESS,
110 | payload: res
111 | })
112 | return res
113 | })
114 | .catch(error=>{
115 | dispatch({
116 | type: APPLY_FILTERS_FAIL,
117 | payload: {error}
118 | })
119 | return error
120 | })
121 | }
122 |
123 | export const APPLY_FILTERS_BEGIN='APPLY_FILTERS_BEGIN'
124 | export const APPLY_FILTERS_SUCCESS='APPLY_FILTERS_SUCCESS'
125 | export const APPLY_FILTERS_FAIL='APPLY_FILTERS_FAIL'
126 |
127 |
128 | export const SEARCH_BEGIN='SEARCH_BEGIN'
129 | export const SEARCH_SUCCESS='SEARCH_SUCCESS'
130 | export const SEARCH_FAIL='SEARCH_FAIL'
131 |
132 |
133 | export const GET_ALL_PRODUCTS_BEGIN='GET_ALL_PRODUCTS_BEGIN'
134 | export const GET_ALL_PRODUCTS_SUCCESS='GET_ALL_PRODUCTS_SUCCESS'
135 | export const GET_ALL_PRODUCTS_FAIL='GET_ALL_PRODUCTS_FAIL'
136 |
137 | export const GET_PRODUCT_BEGIN='GET_PRODUCT_BEGIN'
138 | export const GET_PRODUCT_SUCCESS='GET_PRODUCT_SUCCESS'
139 | export const GET_PRODUCT_FAIL='GET_PRODUCT_FAIL'
140 |
141 | export const GET_PRODUCTS_BY_CATEGORY_BEGIN='GET_PRODUCTS_BY_CATEGORY_BEGIN'
142 | export const GET_PRODUCTS_BY_CATEGORY_SUCCESS='GET_PRODUCTS_BY_CATEGORY_SUCCESS'
143 | export const GET_PRODUCTS_BY_CATEGORY_FAIL='GET_PRODUCTS_BY_CATEGORY_FAIL'
--------------------------------------------------------------------------------
/src/pages/productOverview/ProductOverview.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from './stylesheets/productOverview.module.sass'
3 | import { Button } from 'react-bootstrap'
4 | import Header from '../../components/header/headerContainer'
5 | import Variants from './components/Variants'
6 | import mergeProductAndVariants from './utils/mergeProductAndVariants'
7 | import jumpTo from '../../modules/Navigation'
8 |
9 | export default class ProductOverview extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | color: '',
14 | size: '',
15 | pic: '',
16 | selectedSize: '',
17 | id: ''
18 | }
19 | }
20 | componentDidMount() {
21 | this.props.getProduct(this.props.location.pathname.split("/").slice(-1)[0])
22 | this.props.getVariantsByProductId(this.props.location.pathname.split("/").slice(-1)[0])
23 | }
24 |
25 |
26 | handleClick = (variant) => {
27 | this.setState({
28 | color: variant.color,
29 | size: variant.size,
30 | pic: variant.imagePath,
31 | selectedSize: '',
32 | id: variant._id
33 | })
34 | }
35 |
36 | clickSize = (s) => {
37 | this.setState({
38 | selectedSize: s
39 | })
40 | }
41 |
42 | addToBag = () => {
43 | this.props.postCart(
44 | this.state.id || this.props.location.pathname.split("/").slice(-1)[0]
45 | ).then(res => {
46 | jumpTo('/bag')
47 | })
48 | }
49 |
50 | render() {
51 | return (
52 |
53 |
54 | {this.props.product &&
55 |
56 |
57 | {/* left image */}
58 |
59 |
60 |
61 | {/* right content box */}
62 |
63 |
64 | {/* top descriptions */}
65 |
66 | {this.props.product.title}
67 |
68 |
69 | {this.props.product.description}
70 |
71 |
72 | ${this.props.product.price} CAD
73 |
74 | {/* dotted border */}
75 |
76 | {/* bottom descriptions */}
77 |
78 |
86 |
87 |
88 | Add to Bag
89 | Buy Now
90 |
91 |
92 |
93 |
94 |
95 | }
96 |
97 | )
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/pages/loginsignin/LoginSignin.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from './stylesheets/loginsignin.module.sass'
3 | import Base from './components/Base'
4 | import capitalizeString from './utils/capitalizeString'
5 | import jumpTo from '../../modules/Navigation'
6 | import LoadingAnimation from '../../components/loadingAnimation'
7 |
8 |
9 | export default class LoginSignin extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {}
13 | this.inputText = {}
14 | for (const input of props.INPUT_CONFIG) {
15 | this.state[input.name] = { errorMsg: '' }
16 | this.inputText[input.name] = ''
17 | }
18 | }
19 | //validate input when blur
20 | handleBlur = (e, validResult) => {
21 | const name = e.target.name
22 | this.inputText[name] = e.target.value
23 | if (!validResult.isValid) {
24 | this.setState({
25 | [name]: { errorMsg: validResult.errorMsg }
26 | })
27 | } else {
28 | this.setState({
29 | [name]: { errorMsg: '' }
30 | })
31 | }
32 | }
33 | // when focus, clear error message
34 | handleFocus = (e) => {
35 | const name = e.target.name
36 | this.setState({
37 | [name]: { errorMsg: '' }
38 | })
39 | }
40 | //submit actions
41 | handleClick = () => {
42 | //validate all input
43 | let canSubmit = true
44 | for (const input of this.props.INPUT_CONFIG) {
45 | if (!!!input.validations) continue
46 | for (const v of input.validations) {
47 | let checkResult = v.check(this.inputText[input.name])
48 | canSubmit = canSubmit && checkResult
49 | if (!checkResult) {
50 | this.setState({
51 | [input.name]: { errorMsg: v.errMsg }
52 | })
53 | break
54 | }
55 | }
56 | }
57 | if (!canSubmit) {
58 | console.log('valid fail');
59 | return
60 | }
61 | if (this.props.title === 'Login') {
62 | const { email, password } = this.inputText
63 | this.props.submitAction(email, password)
64 | .then(res => {
65 | jumpTo('/dashboard')
66 | // console.log(res)
67 | // console.log('loginsignin res');
68 | return res
69 | })
70 | .catch(error => {
71 | // console.log('loginsignin error')
72 | // console.log(error.response)
73 | alert(error.response.data.error.message)
74 | return error
75 | })
76 | }
77 | if (this.props.title === 'Signin') {
78 | const { fullname, email, password, verifyPassword } = this.inputText
79 | this.props.submitAction(fullname, email, password, verifyPassword)
80 | .then(res => {
81 | jumpTo('/login')
82 | })
83 | .catch(error => {
84 | alert(error.response.data.error.message)
85 | return error
86 | })
87 | }
88 | }
89 | render() {
90 | return (
91 |
92 |
110 | }
111 | />
112 |
113 |
114 | )
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/Filter_md.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from '../stylesheets/filter_md.module.sass'
3 | import Selection from './Selection'
4 | import SubSelection from './SubSelection'
5 |
6 | export default class Filter_md extends Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | isShow: false
11 | }
12 | }
13 | clickShow = () => {
14 | this.setState(prevState => ({
15 | isShow: !prevState.isShow
16 | }))
17 | }
18 | clickClear = () => {
19 | this.props.clear()
20 | }
21 | render() {
22 | const { onChange, clear, selected_name, configs } = this.props
23 | return (
24 |
25 |
26 |
30 | {this.state.isShow
31 | ?
Close Filter
32 | :
Show Filter
33 | }
34 |
35 |
39 | Clear Filter
40 |
41 |
42 |
43 | {/* order */}
44 |
45 |
46 |
47 |
48 | ORDER
49 |
50 |
51 |
52 | {configs['order'].map(n => {
53 | n = n.toUpperCase()
54 | return (
55 |
62 | )
63 | })}
64 |
65 |
66 | {/* filter */}
67 |
68 |
69 |
70 |
71 | FILTER
72 |
73 |
74 |
75 | {/* department */}
76 |
77 |
0}
80 | onChange={() => { }}
81 | />
82 |
83 |
90 |
91 |
92 | {/* price */}
93 |
94 |
0}
97 | onChange={() => { }}
98 | />
99 |
100 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | )
114 | }
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/src/assets/images/checkmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
26 |
45 |
47 |
48 |
50 | image/svg+xml
51 |
53 |
54 |
55 |
56 |
61 |
69 |
85 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/header/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import styles from './stylesheets/header.module.sass'
3 | import UserHeader from './components/UserHeader'
4 | import Menu from './components/Menu'
5 | import Search from './components/Search'
6 | import jumpTo,{go} from '../../modules/Navigation'
7 | import Auth from '../../modules/Auth'
8 | import device, { size } from '../../modules/mediaQuery'
9 | import MediaQuery from 'react-responsive'
10 |
11 | export default class Header extends Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {
15 | input: '',
16 | isToggle: false
17 | }
18 | }
19 | handleChange = (v) => {
20 | this.setState({
21 | input: v
22 | })
23 | }
24 | handleSuggest = (v) => {
25 | this.setState({
26 | input: v
27 | })
28 | }
29 | handleToggle = () => {
30 | this.setState(prevState => {
31 | return {
32 | isToggle: !prevState.isToggle
33 | }
34 | })
35 | }
36 | closeToggle = () => {
37 | this.setState({
38 | isToggle: false
39 | })
40 | }
41 | render() {
42 | const { user_token,
43 | departments,
44 | search,
45 | getProductsByCategory,
46 | getAllProducts } = this.props
47 | let visibility = "hide"
48 | if (this.state.isToggle) {
49 | visibility = "show"
50 | }
51 | return (
52 |
53 | {/* larger than 768px */}
54 |
55 | {/* top user header */}
56 |
57 |
60 |
61 | {/* menu header */}
62 |
63 |
64 | {/* logo */}
65 |
{
67 | getAllProducts()
68 | jumpTo('/dashboard')
69 | }}
70 | >
71 | Zack Market
72 |
73 |
74 |
75 |
80 |
81 |
82 |
88 |
89 |
90 |
91 | {/* smaller than 768px */}
92 |
93 |
94 |
95 | {/* toggle content */}
96 |
97 |
98 |
99 | MENU
100 |
104 | x
105 |
106 |
107 |
113 |
114 | CATEGORY
115 |
116 |
121 |
122 | CART
123 |
124 |
jumpTo('/bag')}
127 | >
128 | Shopping Bag
129 |
130 |
131 | USER
132 |
133 |
jumpTo('/login')}
136 | >
137 | Login
138 |
139 |
{ Auth.logout(); go('/dashboard') }}
142 | >
143 | Logout
144 |
145 |
146 |
147 | {/* toggle icon */}
148 |
153 |
154 | {/* logo */}
155 |
{
157 | getAllProducts()
158 | jumpTo('/dashboard')
159 | }}
160 | >
161 | Zack Market
162 |
163 |
164 |
165 |
166 | )
167 | }
168 | }
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/src/pages/dashboard/components/Filter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Checkbox from './Checkbox'
3 | import styles from '../stylesheets/filter.module.sass'
4 | import generateFilterString from '../utils/generateFilterString'
5 | import device, { size } from '../../../modules/mediaQuery'
6 | import MediaQuery from 'react-responsive'
7 | import Filter_md from './Filter_md'
8 |
9 | export default class Filter extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.FILTER_CONFIG = {
13 | order: ['Ascending', 'Descending'],
14 | department: ['Men', 'Women'],
15 | price: ['Less Than $29', '$29 - $39', '$39 - $49', '$49 - $89', 'Greater Than $89']
16 | }
17 | this.initialState = {}
18 | this.state = this.initialState
19 | }
20 | handleChange = (e, category, name) => {
21 | let tagName = ''
22 | let isChecked = false
23 | //handle div click
24 | if (name) {
25 | tagName = name.toUpperCase()
26 | isChecked = !!!(this.state[category] && this.state[category].includes(name))
27 | } else {
28 | // handle input checkbox
29 | tagName = e.target.name.toUpperCase()
30 | isChecked = e.target.checked
31 | }
32 | this.setState(prevState => {
33 | //add category value to array
34 | if (isChecked) {
35 | //user can only select one order
36 | if (category === 'order') {
37 | return {
38 | [category]: [tagName]
39 | }
40 | }
41 | return {
42 | [category]: [...prevState[category] || [], tagName]
43 | }
44 | } else {
45 | //remove category value from array
46 | const new_prop_array = prevState[category].filter(n => n !== tagName)
47 | return {
48 | [category]: new_prop_array
49 | }
50 | }
51 | }, () => {
52 | this.props.applyFilters(generateFilterString(this.state))
53 | })
54 | }
55 | handleCloseTag = (category, name) => {
56 | this.setState(prevState => {
57 | const new_prop_array = prevState[category].filter(n => n !== name)
58 | return {
59 | [category]: new_prop_array
60 | }
61 | }, () => this.props.applyFilters(generateFilterString(this.state)))
62 | }
63 | clearAllFilter = () => {
64 | this.setState({ order: [], price: [], department: [] }, () => this.props.applyFilters(''))
65 | }
66 | render() {
67 | return (
68 |
69 |
70 |
71 |
72 |
76 |
77 | {/* order */}
78 |
79 |
80 | ORDER
81 |
82 | {this.FILTER_CONFIG['order'].map(n =>
83 |
90 | )}
91 |
92 | {/* department */}
93 |
94 |
95 | DEPARTMENT
96 |
97 | {this.FILTER_CONFIG['department'].map(n =>
98 |
105 | )}
106 |
107 | {/* price */}
108 |
109 |
110 | PRICE
111 |
112 | {this.FILTER_CONFIG['price'].map(n =>
113 |
120 | )}
121 |
122 |
123 |
124 | Clear All
125 |
126 | {/* filter tags */}
127 |
128 | {
129 | Object.keys(this.state).map(c => (
130 | this.state[c] && this.state[c].map(n => (
131 |
132 |
133 | {n}
134 |
135 |
this.handleCloseTag(c, n)}
138 | >
139 | x
140 |
141 |
142 | ))))}
143 |
144 |
145 |
146 |
147 |
148 |
154 |
155 |
156 | )
157 | }
158 | }
159 |
160 |
--------------------------------------------------------------------------------