├── .gitignore
├── README.md
├── notes todo.txt
├── package-lock.json
├── package.json
├── public
├── SHIQA.png
├── ico.png
├── index.html
├── manifest.json
└── robots.txt
└── src
├── SHIQA.png
├── components
├── Alert
│ └── Index.js
├── Auth
│ ├── Login.js
│ ├── Scss
│ │ └── Index.scss
│ └── SignUp.js
├── Category
│ ├── Add.js
│ ├── Index.js
│ ├── List.js
│ └── scss
│ │ └── index.scss
├── Edit
│ ├── CategoryDropdown.js
│ ├── Index.js
│ ├── PriceSettings.js
│ ├── SegmentsDropdown.js
│ ├── UpdateVariationOptions.js
│ ├── UpdateVariations.js
│ └── scss
│ │ └── index.scss
├── Filter
│ └── Filter.js
├── Header
│ ├── Index.js
│ └── Sass
│ │ └── Style.scss
├── Helpers
│ └── functions.js
├── Hero
│ ├── Index.js
│ └── Sass
│ │ └── Style.scss
├── Order
│ ├── Index.js
│ ├── Product.js
│ ├── Sass
│ │ └── Index.scss
│ └── ViewOrder.js
├── Product
│ ├── AddProducts.js
│ ├── Product.js
│ ├── Products.js
│ └── Sass
│ │ └── Style.scss
├── Segments
│ ├── Add.js
│ ├── Index.js
│ └── List.js
├── Update
│ ├── Index.js
│ ├── Product.js
│ └── Sass
│ │ └── Index.scss
├── Uploads
│ ├── AddVariationOptions.js
│ ├── AddVariations.js
│ ├── CategoryDropdown.js
│ ├── Index.js
│ ├── PriceSettings.js
│ ├── SegmentsDropdown.js
│ └── scss
│ │ └── index.scss
└── WishList
│ ├── Index.js
│ ├── Product.js
│ └── Sass
│ └── Index.scss
├── config
└── firebase.js
├── containers
├── Admin.js
├── Filters.js
├── Home.js
└── Shop.js
├── contexts
├── AuthContext.js
├── ProductContext.js
└── WishListContext.js
├── index.js
├── layouts
└── Sidebar.js
├── logo.svg
├── reducers
├── AuthReducer.js
├── ProductReducer.js
└── WishListReducer.js
├── redux
├── actions
│ └── order
│ │ ├── orderActions.js
│ │ └── orderTypes.js
├── reducers
│ ├── orderReducer.js
│ └── rootReducer.js
└── store.js
├── serviceWorker.js
├── styles
├── global.scss
├── mixins.scss
├── reset.scss
└── variables.scss
└── utils
└── Index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project can be viewed in https://admin-shiqa.netlify.com/.
2 |
3 | ## Admin Shiqa Ecommerce App
4 |
5 | This app is the admin part of Shiqa Shopping ecommerce app, where we can process clients orders, aproved orders, update quantity, update prices, add and remove products, add shipping fee based on orders, upload product images, add and remove categories & segments and many more.
6 |
7 | This app this built in React Js, Redux, Redux-thunk, React-Bootstrap and Firebase BackEnd.
8 |
9 | The client side of this app is a mobile android app which is built in react native, redux, redux-thunk and firebase. Please download the client android app here: https://drive.google.com/file/d/1uW8-p-ovmO2MEgxw1Zz3AcBXTRW9oVWU/view?usp=sharing
10 |
11 |
12 | For testing you can use this temporary user:
13 |
14 | email: test@gmail.com
15 | password: testing
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/notes todo.txt:
--------------------------------------------------------------------------------
1 | 2. process orders
2 | 3. products filter by name for products edits and updates
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-shop",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-svg-core": "^1.2.27",
7 | "@fortawesome/free-solid-svg-icons": "^5.12.1",
8 | "@fortawesome/react-fontawesome": "^0.1.8",
9 | "animate.css": "^3.7.2",
10 | "bootstrap": "^4.4.1",
11 | "firebase": "^7.2.0",
12 | "grpc": "^1.24.2",
13 | "node-sass": "^4.13.1",
14 | "react": "^16.12.0",
15 | "react-bootstrap": "^1.0.0-beta.16",
16 | "react-dom": "^16.12.0",
17 | "react-firebase-file-uploader": "^2.4.3",
18 | "react-input-switch": "^2.2.1",
19 | "react-loader-spinner": "^3.1.5",
20 | "react-notifications-component": "^2.3.0",
21 | "react-redux": "^7.2.0",
22 | "react-router-dom": "^5.1.2",
23 | "react-scripts": "^3.4.0",
24 | "react-uuid": "^1.0.2",
25 | "reactjs-popup": "^1.5.0",
26 | "redux": "^4.0.5",
27 | "redux-devtools-extension": "^2.13.8",
28 | "redux-thunk": "^2.3.0",
29 | "styled-components": "^4.4.1"
30 | },
31 | "scripts": {
32 | "start": "react-scripts start",
33 | "build": "react-scripts build",
34 | "test": "react-scripts test",
35 | "eject": "react-scripts eject"
36 | },
37 | "eslintConfig": {
38 | "extends": "react-app"
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/public/SHIQA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talljohnthin/react-shop/4dac27b84ff4ff48b5d6d0530a3de897702e087c/public/SHIQA.png
--------------------------------------------------------------------------------
/public/ico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talljohnthin/react-shop/4dac27b84ff4ff48b5d6d0530a3de897702e087c/public/ico.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
19 |
20 |
29 | Shiqa Shopping
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/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 | // "src": "logo192.png",
12 | // "type": "image/png",
13 | // "sizes": "192x192"
14 | // },
15 | // {
16 | // "src": "logo512.png",
17 | // "type": "image/png",
18 | // "sizes": "512x512"
19 | // }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/src/SHIQA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/talljohnthin/react-shop/4dac27b84ff4ff48b5d6d0530a3de897702e087c/src/SHIQA.png
--------------------------------------------------------------------------------
/src/components/Alert/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Alert } from 'react-bootstrap'
3 | export default function Index(props) {
4 | const { showAlert, message, type } = props
5 | const alertStyle = {
6 | position: 'fixed',
7 | top: showAlert ? '0' : '-100%',
8 | left: '50%',
9 | transform: 'translateX(-50%)',
10 | transition: 'all ease 0.25s',
11 | zIndex:'9999',
12 | width:'300px',
13 | borderRadius:0
14 | }
15 |
16 | return (
17 |
18 |
19 | { message }
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Auth/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useContext, useState, useEffect } from 'react'
2 | import { Container, Card } from 'react-bootstrap'
3 | import firebase from '../../config/firebase'
4 | import { Redirect } from "react-router-dom";
5 | import { AuthContext } from '../../contexts/AuthContext'
6 | import './Scss/Index.scss'
7 |
8 | const Login = () => {
9 |
10 | const [email, setEmail] = useState('')
11 | const [password, setPassword] = useState('')
12 | const [redirect, setRedirect] = useState(false)
13 | const [errorMessage, setErrorMessage] = useState('')
14 | const [successMessage, setSuccessMessage] = useState('')
15 | const [isLoading, setIsLoading] = useState(false)
16 | const { state, dispatch } = useContext(AuthContext)
17 |
18 | if (redirect) {
19 | return
20 | }
21 |
22 | const handleLogin = async(e) => {
23 | e.preventDefault()
24 | setIsLoading(true)
25 | if ( email === '' || password === '') {
26 | setErrorMessage('Email and Password cannot be empty.')
27 | setIsLoading(false)
28 | return
29 | }
30 | const user = await firebase.auth().signInWithEmailAndPassword(email, password).catch( err => {
31 | setIsLoading(false)
32 | setErrorMessage(err.message)
33 | return null
34 | })
35 | if ( user ) {
36 | dispatch({
37 | type: "LOGIN",
38 | payload: user
39 | })
40 | setIsLoading(false)
41 | setErrorMessage('')
42 | setSuccessMessage('Login Successfully!')
43 | setTimeout(()=>{
44 | setRedirect(true)
45 | },400)
46 | }
47 | }
48 |
49 | return (
50 |
51 |
52 |
53 |
54 | Admin User Login
55 |
56 |
57 |
74 |
75 |
76 |
77 |
78 | )
79 | }
80 |
81 |
82 | export default Login
83 |
--------------------------------------------------------------------------------
/src/components/Auth/Scss/Index.scss:
--------------------------------------------------------------------------------
1 | .login-container {
2 | max-width:380px;
3 | margin:0 auto;
4 | padding:40px 20px;
5 | ._loading_overlay_wrapper {
6 | background:rgba(0,0,0, 0.4);
7 |
8 | }
9 | label {
10 | margin-bottom:5px;
11 | }
12 | input {
13 | margin-bottom:15px;
14 | }
15 | .btn-primary {
16 | float:right;
17 | width:100%;
18 | display: block;
19 | }
20 | .alert {
21 | display: block;
22 | width:100%;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/components/Auth/SignUp.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useContext, useState } from 'react'
2 | import { Container, Card } from 'react-bootstrap'
3 | import firebase from '../../config/firebase'
4 | import { Redirect } from "react-router-dom";
5 |
6 | const SignUp = () => {
7 |
8 | const [email, setEmail] = useState('')
9 | const [password, setPassword] = useState('')
10 | const [name, setName] = useState('')
11 | const [phone, setPhone] = useState('')
12 | const [redirect, setRedirect] = useState(false)
13 | const [errorMessage, setErrorMessage] = useState('')
14 | const [successMessage, setSuccessMessage] = useState('')
15 | const [isLoading, setIsLoading] = useState(false)
16 |
17 | if (redirect) {
18 | return
19 | }
20 |
21 | const handleSignUp = (e) => {
22 | e.preventDefault()
23 |
24 | if( email && password) {
25 | setIsLoading(true)
26 | firebase.auth().createUserWithEmailAndPassword(email,password)
27 | .then((result) => {
28 | setPassword('')
29 | setEmail('')
30 | return result.user.updateProfile({
31 | displayName: name,
32 | phoneNumber: phone
33 | }).then(() => {
34 | setName('')
35 | setPhone('')
36 | setIsLoading(false)
37 | setSuccessMessage('Signed Up Successfully')
38 | setTimeout(()=>{
39 | setRedirect(true)
40 | },300)
41 | })
42 | })
43 | .catch( error => {
44 | setErrorMessage(error)
45 | setIsLoading(false)
46 | })
47 | }else {
48 | setErrorMessage('Email & Password cannot be empty!')
49 | }
50 | }
51 | return (
52 |
53 |
54 |
55 |
56 | Add New Admin User
57 |
58 |
59 |
79 |
80 |
81 |
82 |
83 |
84 | )
85 | }
86 |
87 |
88 | export default SignUp
89 |
--------------------------------------------------------------------------------
/src/components/Category/Add.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import firebase from 'firebase/app'
3 | import FileUploader from "react-firebase-file-uploader"
4 | import { Button, Form, Modal } from 'react-bootstrap'
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6 | import { faPlus, faSave, faTimes } from '@fortawesome/free-solid-svg-icons'
7 | import './scss/index.scss'
8 |
9 | export default class Add extends Component {
10 | constructor(props) {
11 | super(props)
12 | this.state = {
13 | category: '',
14 | show:false,
15 | isUploading: false,
16 | progress: 0,
17 | imageURL: ""
18 | }
19 | }
20 | handleUpdateStateCategory = e => {
21 | e.preventDefault()
22 | this.setState({
23 | category: e.target.value
24 | })
25 | }
26 | handleFormSubmit = e => {
27 | e.preventDefault()
28 | this.props.handleAddNewCategory(this.state.category, this.state.imageURL)
29 | this.setState({
30 | category:''
31 | })
32 |
33 | }
34 | handleClose = () => {
35 | this.setState({
36 | show:false
37 | })
38 | }
39 | handleShow = () => {
40 | this.setState({
41 | show:true
42 | })
43 | }
44 | handleUploadStart = () => this.setState({ isUploading: true, progress: 0 });
45 | handleProgress = progress => this.setState({ progress });
46 | handleUploadError = error => {
47 | this.setState({ isUploading: false });
48 | console.error(error);
49 | };
50 | handleUploadSuccess = filename => {
51 | this.setState({ progress: 100, isUploading: false });
52 | firebase
53 | .storage()
54 | .ref("images")
55 | .child(filename)
56 | .getDownloadURL()
57 | .then(url => this.setState({ imageURL: url }));
58 | };
59 | render() {
60 | return (
61 |
62 |
65 |
70 |
103 |
104 |
105 | )
106 | }
107 | }
108 |
109 |
110 | const addButton = {
111 | marginTop:'10px',
112 | float:'right',
113 | backgroundColor:'#00807d',
114 | borderColor:'#00807d'
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/src/components/Category/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { ListGroup, Card } from 'react-bootstrap'
3 | import Container from 'react-bootstrap/Container'
4 | import List from './List'
5 | import Add from './Add'
6 | import { db } from '../../config/firebase'
7 | import Hero from './../Hero/Index'
8 | import Alert from '../Alert/Index'
9 | import { Redirect } from 'react-router-dom'
10 | import { AuthContext } from './../../contexts/AuthContext'
11 |
12 | const container = {
13 | padding:30,
14 | paddingTop:0
15 | }
16 |
17 | export default class Index extends Component {
18 | state = {
19 | categories: [],
20 | alertMessage: null,
21 | showAlert: false,
22 | alertType: 'success'
23 | }
24 | alertTimeout = null
25 |
26 | componentDidMount() {
27 | db.collection("category")
28 | .onSnapshot(snapshot => {
29 | const categories = []
30 | snapshot.forEach(doc => {
31 | const obj = {
32 | id: doc.id,
33 | name: doc.data()
34 | }
35 | categories.push(obj)
36 | })
37 | this.setState({ categories })
38 | });
39 | this.setState({ isLoading: true })
40 | db.collection("category")
41 | .onSnapshot(snapshot => {
42 | const categoryList = []
43 | snapshot.forEach(doc => {
44 | const dataObj = {
45 | id: doc.id,
46 | name: doc.data()
47 | }
48 | categoryList.push(dataObj)
49 |
50 | })
51 | this.setState({
52 | categoryList,
53 | isLoading: false
54 | })
55 | })
56 | }
57 | handleAddNewCategory = (newCategory, categoryImage) => {
58 | if (newCategory.length > 3 && categoryImage.length) {
59 | db.collection("category").add({
60 | name: newCategory,
61 | url:categoryImage
62 | })
63 | .then(function (docRef) {
64 | console.log("Document written with ID: ", docRef.id);
65 | })
66 | .catch(function (error) {
67 | console.error("Error adding document: ", error);
68 | });
69 | const alertObj = {
70 | type: 'success',
71 | message: 'New Category Added Successfuly!'
72 | }
73 | if (this.alertTimeout) {
74 | clearTimeout(this.alertTimeout)
75 | this.setState({
76 | showAlert: false
77 | }, () => {
78 | this.alertTimeout = setTimeout(() => {
79 | this.handleShowAlert(alertObj)
80 | }, 250)
81 | })
82 | } else {
83 | this.handleShowAlert(alertObj)
84 | }
85 | } else {
86 | const alertObj = {
87 | type: 'failed',
88 | message: 'Character must be greater than 3 and It must have a category image'
89 | }
90 | if (this.alertTimeout) {
91 | clearTimeout(this.alertTimeout)
92 | this.setState({
93 | showAlert: false
94 | }, () => {
95 | this.alertTimeout = setTimeout(() => {
96 | this.handleShowAlert(alertObj)
97 | }, 250)
98 | })
99 | } else {
100 | this.handleShowAlert(alertObj)
101 | }
102 | }
103 | }
104 | handleShowAlert = ({ message, type }) => {
105 | this.setState({
106 | showAlert: true,
107 | alertMessage: message,
108 | alertType: type
109 | }, () => {
110 | this.alertTimeout = setTimeout(() => {
111 | this.setState({
112 | showAlert: false,
113 | })
114 | }, 3000)
115 | })
116 | }
117 | handleRemoveCategory = (id) => {
118 | db.collection("category").doc(id).delete().then(function () {
119 | console.log('deleted')
120 | }).catch(function (error) {
121 | console.error("Error removing document: ", error);
122 | });
123 | const alertObj = {
124 | type: 'success',
125 | message: 'Category Deleted Successfuly!'
126 | }
127 | if (this.alertTimeout) {
128 | clearTimeout(this.alertTimeout)
129 | this.setState({
130 | showAlert: false
131 | }, () => {
132 | this.alertTimeout = setTimeout(() => {
133 | this.handleShowAlert(alertObj)
134 | }, 250)
135 | })
136 | } else {
137 | this.handleShowAlert(alertObj)
138 | }
139 | }
140 |
141 | render() {
142 |
143 | const isLoading = this.state.isLoading ?
144 | (Loading Categories...
) :
145 | (
149 | )
150 |
151 | return (
152 |
153 |
154 | {(value) => {
155 | if(Object.keys(value.state.user).length === 0 || value.state.user === null) {
156 | return
157 | }
158 | }}
159 |
160 |
161 |
162 |
167 |
168 |
169 | List of Categories
170 |
171 |
172 |
173 | {isLoading}
174 |
175 |
178 |
179 |
180 |
181 |
182 | )
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/components/Category/List.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, Component } from 'react'
2 | import { Modal , Button, ListGroup, Badge } from 'react-bootstrap'
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4 | import { faTrashAlt, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons'
5 | export default class List extends Component {
6 |
7 | state = {
8 | show:false,
9 | idToRemove:null
10 | }
11 |
12 | removeCategory = () => {
13 | if(this.state.idToRemove) {
14 | this.props.handleRemoveCategory(this.state.idToRemove)
15 | this.setState({
16 | show:false
17 | })
18 | }
19 | }
20 | handleClose = () => {
21 | this.setState({
22 | show:false
23 | })
24 | }
25 | handleShow(id) {
26 | this.setState({
27 | show:true,
28 | idToRemove: id
29 | })
30 | }
31 | render() {
32 | const list = this.props.categoryList.map((doc, index) => {
33 | return ( {index + 1} {doc.name.name}
34 | this.handleShow(doc.id)}
35 | style={{float:'right', cursor:'pointer'}}/>
36 | )
37 | })
38 | return (
39 |
40 | { list != '' ? list : 'No category at this moment' }
41 |
47 |
48 | Delete Category
49 |
50 |
51 |
52 | Are you sure you want to delete?
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | )
62 | }
63 | }
64 |
65 |
66 | const removeButton = {
67 | backgroundColor:'#f53d2d',
68 | borderColor:'#f53d2d'
69 | }
--------------------------------------------------------------------------------
/src/components/Category/scss/index.scss:
--------------------------------------------------------------------------------
1 | .category-image {
2 | width:100%;
3 | margin-top:15px;
4 | }
5 | .category-file-uploader {
6 | margin:20px 0;
7 | }
--------------------------------------------------------------------------------
/src/components/Edit/CategoryDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Form } from 'react-bootstrap'
3 |
4 | export default function CategoryDropdown(props) {
5 | const categories = props.categories
6 | const populateDropdownCategory = (categories) => {
7 | if (categories) {
8 | return categories.map(category => )
9 | }
10 | }
11 | const addSelectedCategory = (category) => {
12 | props.handleAddSectedCategoryToState(category)
13 | }
14 | return (
15 |
16 |
17 | Select Category:
18 | addSelectedCategory(e.target.value) } as="select">
19 |
20 | { categories ? populateDropdownCategory(categories) : ( ) }
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Edit/PriceSettings.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react'
2 | import {FormControl, Form} from 'react-bootstrap'
3 |
4 | export default function PriceSettings ({variationsValue, variation, variationIndex, variationOptions, setPrice, setAvailability, priceOptions}) {
5 | let mapOptions = null
6 | const handleSetPrice = (variationIndex, index, value) => {
7 | setPrice(variationIndex, index, value)
8 | }
9 | const handleSetAvailability = (variationIndex, index, value) => {
10 | setAvailability(variationIndex, index, value)
11 | }
12 | if (variationOptions && variationsValue) {
13 |
14 | mapOptions = variationOptions.map( (o,i) => {
15 |
16 | const price = priceOptions[variationIndex].options[i].price,
17 | status = priceOptions[variationIndex].options[i].is_available
18 | return (
19 |
20 | - { o.option }
21 | - handleSetPrice(variationIndex, o.optionIndex, e.target.value)}>
22 | -
23 | handleSetAvailability(variationIndex, o.optionIndex, e.target.value)}>
24 |
25 |
26 |
27 |
28 |
29 | )
30 | })
31 | }
32 | return (
33 |
34 |
35 |
{variation}
36 |
37 |
38 | - Option
39 | - Selling Price
40 | - Availabity
41 |
42 | { mapOptions }
43 |
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Edit/SegmentsDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Form } from 'react-bootstrap'
3 | export default function SegmentDropdown(props) {
4 | const segments = props.segments
5 | const populateDropdownSegment = (segments) => {
6 | if (segments) {
7 | return segments.map(segment => )
8 | }
9 | }
10 | const addSelectedSegment = (segment) => {
11 | props.handleAddSectedSegmentToState(segment)
12 | }
13 | return (
14 |
15 |
16 | Select Segments:
17 | addSelectedSegment(e.target.value)} as="select">
18 |
19 | {segments ? populateDropdownSegment(segments) : ()}
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Edit/UpdateVariationOptions.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Button, FormControl } from 'react-bootstrap'
3 | import { FontAwesomeIcon} from '@fortawesome/react-fontawesome'
4 | import { faTrash } from '@fortawesome/free-solid-svg-icons'
5 |
6 |
7 | export default function UpdateVariationOptions(props) {
8 | return (
9 |
10 | Fields of options:
11 |
12 | {props.variationOptions.map((option, index) => - props.handleAddVariationOptionValue(e, index) }/>
13 | props.handleRemoveVariationOption(index) }/>
14 |
)}
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Edit/UpdateVariations.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Button, FormControl } from 'react-bootstrap'
3 | import { FontAwesomeIcon} from '@fortawesome/react-fontawesome'
4 | import { faTrash } from '@fortawesome/free-solid-svg-icons'
5 |
6 |
7 | export default function UpdateVariations(props) {
8 | return (
9 |
10 | Fields of variations:
11 |
12 | {props.variations.map((variation, index) => - props.handleAddVariationValue(e, index) }/>
13 | props.handleRemoveVariation(index) }/>
14 |
)}
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Edit/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import './../../../styles/variables.scss';
2 |
3 | .cards {
4 | display: flex;
5 | justify-content: space-between;
6 | flex-wrap: wrap;
7 | position: relative;
8 | & > div {
9 | width:48%;
10 | margin-bottom:25px;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | position: relative;
15 | img {
16 | width: 100%;
17 | }
18 | }
19 | .card {
20 | width:48%;
21 | margin-bottom:25px;
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | position: relative;
26 | }
27 | .card-cover {
28 | position: relative;
29 | &:before {
30 | content:'Cover';
31 | width:100%;
32 | height:100%;
33 | position: absolute;
34 | left:0;
35 | right:0;
36 | z-index: 1;
37 | background:rgba($secondary-color, 0.4);
38 | color:#fff;
39 | display: flex;
40 | font-size:18px;
41 | font-family: $secondary-font;
42 | font-weight: 100;
43 | align-items: center;
44 | justify-content: center;
45 | }
46 | }
47 | Button.card-close {
48 | width:30px;
49 | height:30px;
50 | border-radius:0;
51 | position: absolute;
52 | top:0;
53 | right:0;
54 | padding:0;
55 | font-size:14px;
56 | z-index: 3;
57 | }
58 | }
59 |
60 | // variations
61 | .variation-list {
62 | padding:0;
63 | li {
64 | display: flex;
65 | align-items: center;
66 | list-style-type: none;
67 | margin:0 0 10px;
68 | input {
69 | margin-right: 8px;
70 | }
71 | svg {
72 | cursor: pointer;
73 | }
74 | }
75 | }
76 |
77 | // Price Settings
78 | .each-variation {
79 | li {
80 | list-style-type: none;
81 | border:1px solid #efefef;
82 | flex:1;
83 | &:not(:last-child) {
84 | border-right:0;
85 | }
86 | }
87 | .each-variation-table-items {
88 | display: flex;
89 | }
90 | }
91 |
92 | // container
93 | div.add-entry-products-container {
94 | margin:0 15px !important;
95 | .btn-primary {
96 | background:$secondary-color;
97 | border:1px solid $secondary-color;
98 | display: flex;
99 | align-items:center;
100 | justify-content: center;
101 | //width:100%;
102 | &:focus {
103 | border:0;
104 | outline:0;
105 | }
106 | }
107 | .variation-title,
108 | .option-title,
109 | .price-title {
110 | @include font-size(15px, 16px, 500px, 1280px);
111 | font-family: $secondary-font;
112 | margin-bottom:8px;
113 | }
114 | .option-title,
115 | .price-title {
116 | margin-top: 10px;
117 | }
118 | // set price
119 | .each-variation {
120 | margin-bottom:15px;
121 | }
122 | .each-variation-title {
123 | text-transform: uppercase;
124 | margin-bottom: 10px;
125 | display: block;
126 | }
127 | .each-variation-table-items {
128 | li {
129 | padding:6px;
130 | display: flex;
131 | align-items: center;
132 | justify-content: center;
133 | text-align: center;
134 | }
135 | }
136 |
137 | .btn-product-save {
138 | border-color:#f53d2d;
139 | background:#f53d2d;
140 | }
141 | }
142 |
143 | // form
144 | .add-entry-form {
145 | @include font-size(15px, 16px, 500px, 1280px);
146 | font-family: $secondary-font;
147 | .form-control {
148 | @include font-size(15px, 16px, 500px, 1280px);
149 | }
150 | .form-label {
151 | margin-bottom:6px;
152 | font-family: $secondary-font;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/components/Filter/Filter.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Filter() {
4 | return (
5 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Header/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useContext} from 'react'
2 | import firebase from '../../config/firebase'
3 | import Container from 'react-bootstrap/Container'
4 | import { Link } from 'react-router-dom'
5 | import { AuthContext } from './../../contexts/AuthContext'
6 | import { WishListContext} from './../../contexts/WishListContext'
7 | import logo from './../../SHIQA.png'
8 | import './Sass/Style.scss'
9 |
10 | const Index = () => {
11 | const {dispatch} = useContext(AuthContext)
12 | const {wishListState} = useContext(WishListContext)
13 | const handleLogout = () => {
14 | dispatch({type: "LOGOUT", payload:{}})
15 | firebase.auth().signOut()
16 | }
17 | let wishListCount = null
18 | if ( wishListState) {
19 | wishListCount = wishListState.products.length <= 0 ? {opacity:0} : {opacity:1};
20 | }
21 | return (
22 |
23 |
24 |
25 |
26 |

27 |
28 |
29 | {/*
30 |
31 |
39 |
40 |
41 | { wishListState.products.length }
42 |
43 |
44 |
45 |
52 |
2
53 |
*/}
54 |
55 |
56 | {(value) => {
57 |
58 | if ( value.state.user === null || Object.keys(value.state.user).length === 0 ) {
59 | return
60 | Register
61 | Login
62 |
63 | } else {
64 | return
65 |
66 | {/* {firebase.auth().currentUser && firebase.auth().currentUser.displayName}
*/}
67 | handleLogout()}>Logout
68 |
69 | }
70 | }}
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default Index;
81 |
82 | const style = {
83 | opacity: 1,
84 | vectorEffect: 'none',
85 | fill: '#000000',
86 | fillOpacity: 1
87 | }
--------------------------------------------------------------------------------
/src/components/Header/Sass/Style.scss:
--------------------------------------------------------------------------------
1 | @import './../../../styles/variables.scss';
2 |
3 |
4 | .header {
5 | background:#fff;
6 | position: fixed;
7 | top:0;
8 | left:0;
9 | width:100%;
10 | z-index: 99999;
11 | overflow: hidden;
12 | box-shadow: 0 0 10px 1px rgba(68,102,242,.05);
13 | .container {
14 | height: 64px;
15 | display: flex;
16 | font-family: 'Roboto Slab', serif;
17 | justify-content: space-between;
18 | align-items: center;
19 | }
20 | .left {
21 | .logo {
22 | max-width: 150px;
23 | }
24 | }
25 | .right {
26 | display: flex;
27 | align-items: center;
28 | .wishlist {
29 | margin-right:12px;
30 | position: relative;
31 | svg {
32 | font-size:16px;
33 | }
34 | .wishlist-count {
35 | position: absolute;
36 | top:0;
37 | right:-7px;
38 | background: $secondary-color;
39 | width:15px;
40 | height:15px;
41 | border-radius: 50%;
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | font-family: $secondary-font;
46 | font-size:10px;
47 | color:#fff;
48 | pointer-events: none;
49 | }
50 | }
51 | .orders {
52 | position: relative;
53 | svg {
54 | font-size:18px;
55 | }
56 | .orders-count {
57 | position: absolute;
58 | top:0;
59 | right:-7px;
60 | background: $secondary-color;
61 | width:15px;
62 | height:15px;
63 | border-radius: 50%;
64 | display: flex;
65 | align-items: center;
66 | justify-content: center;
67 | font-family: $secondary-font;
68 | font-size:10px;
69 | color:#fff;
70 | }
71 | }
72 | .user-login {
73 | margin-left: 15px;
74 | color:#fff;
75 | display: flex;
76 | a {
77 | color:#fff;
78 | text-decoration: none;
79 | }
80 | .user-button {
81 | cursor: pointer;
82 | font-family: $secondary-font;
83 | background:$secondary-color;
84 | padding:10px 20px;
85 | border-radius: 4px;
86 | color:'#fff' !important;
87 | font-size: 15px;
88 | }
89 | }
90 |
91 | }
92 | }
--------------------------------------------------------------------------------
/src/components/Helpers/functions.js:
--------------------------------------------------------------------------------
1 | import { store } from 'react-notifications-component';
2 |
3 | const notification = (title, message, type) => {
4 | store.addNotification({
5 | title,
6 | message,
7 | type, // 'default', 'success', 'info', 'warning'
8 | container: 'top-center', // where to position the notifications
9 | animationIn: ["animated", "fadeIn"], // animate.css classes that's applied
10 | animationOut: ["animated", "fadeOut"], // animate.css classes that's applied
11 | dismiss: {
12 | duration: 1500
13 | }
14 | })
15 | }
16 |
17 | export default notification
--------------------------------------------------------------------------------
/src/components/Hero/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import Container from 'react-bootstrap/Container'
3 | import './Sass/Style.scss'
4 |
5 | export default class Index extends Component {
6 | constructor(props) {
7 | super(props)
8 | }
9 | render() {
10 | return (
11 |
12 |
13 |
14 | {this.props.title}
15 |
16 |
17 |
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Hero/Sass/Style.scss:
--------------------------------------------------------------------------------
1 | @import './../../../styles/mixins.scss';
2 | $primary-color: #F6F8FA;
3 | $secondary-color:#39A7AB;
4 | $primary-font: 'PT Sans', sans-serif;
5 | $secondary-font:'Roboto', sans-serif;
6 |
7 | .hero {
8 | height: 80px;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | width: 100%;
13 | padding:0 15px;
14 | @media (max-width:767px) {
15 | height: 60px;
16 | }
17 | h1 {
18 | text-align: left;
19 | @include font-size(19px, 19px, 500px, 1280px);
20 | font-family: $primary-font;
21 | letter-spacing: 0.5px;
22 | font-weight: 700;
23 | text-transform: uppercase;
24 | }
25 | }
--------------------------------------------------------------------------------
/src/components/Order/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect } from 'react'
2 | import { ListGroup, Form, Button, Card } from 'react-bootstrap'
3 | import Container from 'react-bootstrap/Container'
4 | import { connect } from 'react-redux'
5 | import { getOrders, getOrdersByCustomer, selectOrder } from './../../redux/actions/order/orderActions'
6 | import { formatOrderDate, Spinner } from './../../utils/Index'
7 | import { Redirect } from 'react-router-dom'
8 | import { AuthContext } from './../../contexts/AuthContext'
9 |
10 | const Index = ({ stateOrders, getOrders, getOrdersByCustomer, selectOrder }) => {
11 | let isMounted = false
12 | const [ selectionOrder, setSelectionOrder ] = useState('To Review')
13 | const [ customerId, setCustomerId ] = useState('')
14 | const [redirect, setRedirect] = useState(false)
15 | useEffect(()=>{
16 | isMounted = true
17 | if(stateOrders.length === 0) {
18 | getOrders('On Review')
19 | }
20 | return () => {
21 | isMounted = false
22 | }
23 | }, [])
24 |
25 | const handleCheckOrder = id => {
26 | if(id) {
27 | selectOrder(id)
28 | setRedirect(true)
29 | }
30 | }
31 |
32 | if (redirect) {
33 | return
34 | }
35 |
36 | const onRadioChange = (e) => {
37 | setSelectionOrder(e.target.value)
38 | switch(e.target.value) {
39 | case 'To Review':
40 | getOrders('On Review')
41 | break;
42 | case 'To Proccess':
43 | getOrders('On Ship')
44 | break;
45 | case 'Completed':
46 | getOrders('Received')
47 | break;
48 | default:
49 | return
50 | }
51 | }
52 |
53 | const handleSearchOrderByCustomer = () => {
54 | switch(selectionOrder) {
55 | case 'To Review':
56 | getOrdersByCustomer('On Review', customerId)
57 | break;
58 | case 'To Proccess':
59 | getOrdersByCustomer('On Ship', customerId)
60 | break;
61 | case 'Completed':
62 | getOrdersByCustomer('Received', customerId)
63 | break;
64 | default:
65 | return
66 | }
67 | }
68 |
69 | const listOfOrders = stateOrders.length < 1 ?
70 | Loading items...
:
71 | stateOrders.map((e,i) => {
72 | const orderDate = formatOrderDate(e.name.order_date)
73 | return handleCheckOrder(e.id) }>
76 | { orderDate }
77 |
78 |
79 | })
80 | return (
81 |
82 |
83 | {(value) => {
84 | if(Object.keys(value.state.user).length === 0 || value.state.user === null) {
85 | return
86 | }
87 | }}
88 |
89 |
90 |
Search by order ID:
91 |
92 | setCustomerId(e.currentTarget.value)} />
93 |
95 |
96 |
97 |
98 |
99 |
129 |
130 | { listOfOrders }
131 |
132 |
133 |
134 | )
135 | }
136 |
137 | const mapStateToProps = (state) => ({
138 | stateOrders :state.orders.orders
139 | })
140 |
141 | const mapDispatchToProps = { getOrders, getOrdersByCustomer, selectOrder }
142 | export default connect(mapStateToProps, mapDispatchToProps)(Index)
143 |
144 | const container = {
145 | padding:30,
146 | }
147 | const formRadio = {
148 | display:'flex',
149 | justifyContent:'row',
150 | paddingBottom:15
151 | }
--------------------------------------------------------------------------------
/src/components/Order/Product.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useContext, useState, useEffect } from 'react'
2 | import { formatMoney } from './../../utils/Index'
3 | import { connect } from 'react-redux'
4 | import { addQuantity, sumProductsInOrder, subtractQuantity , removeToOrder} from './../../redux/actions/order/orderActions'
5 |
6 | import './Sass/Index.scss'
7 |
8 | const Product = (props) => {
9 | const { cover, name, variation, option, qty, price, total } = props.product
10 |
11 | const handleAddQty = () => {
12 | if(props.index !== null || props.index !== undefined) {
13 | props.addQuantity(props.index)
14 | props.sumProductsInOrder()
15 | }
16 | }
17 | const handleSubtractQty = () => {
18 | if(props.index !== null || props.index !== undefined) {
19 | props.subtractQuantity(props.index)
20 | props.sumProductsInOrder()
21 | }
22 | }
23 | const handleRemove = () => {
24 | if(props.index !== null || props.index !== undefined) {
25 | props.removeToOrder(props.index)
26 | props.sumProductsInOrder()
27 | }
28 | }
29 |
30 | return (
31 |
32 |
33 |
34 |
35 | {

}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
{ name }
43 |
Variation: { variation }
44 |
Option: { option }
45 |
46 |
Qty: { qty }
47 |
48 |
handleSubtractQty()}>
49 |
{ qty }
50 |
handleAddQty()}>
51 |
52 |
53 |
Price:₱ { formatMoney(Number(price)) }
54 |
Total:₱ { formatMoney(Number(total)) }
55 |
56 |
57 |
58 |
59 | )
60 | }
61 |
62 | const mapDispatchToprops = {addQuantity, sumProductsInOrder, subtractQuantity, removeToOrder}
63 |
64 | export default connect(null, mapDispatchToprops)(Product)
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/components/Order/Sass/Index.scss:
--------------------------------------------------------------------------------
1 | @import './../../../styles/variables.scss';
2 |
3 | .WISHLIST-product {
4 | max-width:600px;
5 | margin:40px auto;
6 | }
7 |
8 | .WISHLIST-product-list .card {
9 | margin-bottom:12px;
10 | border-radius: 4px;
11 | border:0;
12 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
13 | transition:0.3s;
14 | &:hover{
15 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.06);
16 | }
17 | .card-img {
18 | border-radius: 0;
19 | }
20 | .card-total {
21 | font-weight: 700;
22 | font-family: $primary-font;
23 | font-size:14px;
24 | margin-top: 5px;
25 | margin-bottom: 5px;
26 | color:#333 !important;
27 | @media (max-width:767px) {
28 | font-size:14px;
29 | }
30 | }
31 | .card-title {
32 | color:#000;
33 | font-family: $secondary-font;
34 | font-size:15px;
35 | font-weight: 600;
36 | margin:0 0 5px;
37 | text-transform: capitalize;
38 | }
39 | .card-variation,
40 | .card-option,
41 | .card-price,
42 | .card-unit,
43 | .card-total {
44 | margin-bottom: 5px;
45 | font-size:13px;
46 | font-family: $secondary-font;
47 | display: flex;
48 | color:#343a408a;
49 | &:not(:last-child) {
50 | margin-bottom: 6px;
51 | }
52 | span {
53 | flex:1;
54 | text-transform: capitalize !important;
55 | &:first-child {
56 | max-width: 80px;
57 | text-transform: capitalize !important;
58 |
59 | }
60 | }
61 | }
62 | .card-wrapper {
63 | display: flex;
64 | align-items: center;
65 | position: relative;
66 | }
67 | .left {
68 | max-width:160px;
69 | min-height: 157px;
70 | }
71 | .right {
72 | width:100%;
73 | padding-left: 15px;
74 | padding-right: 15px;
75 | .card-units-wrapper {
76 | display: flex;
77 | align-items: center;
78 | ion-icon {
79 | width:16px;
80 | height:16px;
81 | display: flex;
82 | align-items: center;
83 | justify-content: center;
84 | background:$secondary-color;
85 | color:#fff;
86 | cursor: pointer;
87 | border-radius: 2px;
88 | }
89 | .card-units {
90 | font-size:13px;
91 | width:16px;
92 | height:16px;
93 | margin:0 3px;
94 | display: flex;
95 | align-items: center;
96 | justify-content: center;
97 | font-weight: 700;
98 | }
99 | }
100 | .card-remove {
101 | position:absolute;
102 | right:8px;
103 | top:8px;
104 | font-size: 22px !important;
105 | color:$secondary-color;
106 | cursor: pointer;
107 | }
108 | }
109 | .change-variation,
110 | .change-option {
111 | color:$secondary-color;
112 | cursor:pointer;
113 | margin-left:6px;
114 | }
115 | }
116 |
117 | .WISHLIST-product-total {
118 | background:#fff;
119 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
120 | margin-bottom: 12px;
121 | margin-top: 20px;
122 | border-radius: 4px;
123 | overflow: hidden;
124 | .title {
125 | background:$secondary-color;
126 | color:#fff;
127 | padding: 10px 20px;
128 | font-size:16px;
129 | font-family: $secondary-font;
130 | }
131 | ul {
132 | padding:20px;
133 | li {
134 | display: flex;
135 | margin-bottom: 8px;
136 | span {
137 | flex:1;
138 | text-align: right;
139 | }
140 | input[type='number'] {
141 | max-width: 120px;
142 | border:0;
143 | border-bottom:2px solid #eee;
144 | text-align: right;
145 | -webkit-appearance: none;
146 | -moz-appearance: none;
147 | appearance: none;
148 | margin: 0;
149 | }
150 | &:first-child {
151 | .amount {
152 | font-size:16px;
153 | font-weight: 600;
154 | width: 100%;
155 | }
156 | }
157 | &:last-child {
158 | .amount {
159 | font-size:16px;
160 | text-transform: uppercase;
161 | width: 100%;
162 | max-width: 120px;
163 | font-weight: 700;
164 | }
165 | }
166 | }
167 | .label {
168 | font-size:16px;
169 | }
170 | .amount {
171 | }
172 | }
173 | }
174 |
175 | .WISHLIST-shipping-info {
176 | margin-bottom:12px;
177 | border-radius: 0;
178 | border:0;
179 | background:#fff;
180 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
181 | .title {
182 | background:$secondary-color;
183 | color:#fff;
184 | padding: 10px 20px;
185 | font-size:14px;
186 | font-family: $secondary-font;
187 | }
188 | form {
189 | padding:20px;
190 | }
191 | .form-check {
192 | margin-bottom: 20px;
193 | input {
194 | margin-top:3px;
195 | }
196 | label {
197 | font-size:13px;
198 | font-family: $secondary-font;
199 | }
200 | }
201 | .each {
202 | margin-bottom: 8px;
203 | .label {
204 | font-size:13px;
205 | margin-bottom: 8px;
206 | }
207 | .form-group {
208 | margin:0;
209 | }
210 | .form-control {
211 | font-size: 13px;
212 | font-family: $secondary-font;
213 | border:1px solid #eee;
214 | border-radius: 0;
215 | }
216 | }
217 | }
218 |
219 | .procees-order-button {
220 | display: flex;
221 | justify-content: flex-end;
222 | button {
223 | background:$secondary-color;
224 | border-color:$secondary-color;
225 | &:hover {
226 | background-color: rgb(245, 61, 45);
227 | border-color: rgb(245, 61, 45);
228 | }
229 | }
230 | }
--------------------------------------------------------------------------------
/src/components/Order/ViewOrder.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from 'react'
2 | import { connect } from 'react-redux'
3 | import Container from 'react-bootstrap/Container'
4 | import { Redirect } from 'react-router-dom'
5 | import { formatMoney } from './../../utils/Index'
6 | import Hero from './../Hero/Index'
7 | import { sumProductsInOrder, updateShippingFee, proccessOrder} from './../../redux/actions/order/orderActions'
8 | import Product from './Product'
9 |
10 | const ViewOrder = ({ selectedOrder, selectedOrderId, sumProductsInOrder, updateShippingFee, proccessOrder }) => {
11 | const order = selectedOrder
12 | const [shippingFee, setShippingFee] = useState(0)
13 |
14 | useEffect(()=>{
15 | setShippingFee(order.name?.shipping_fee)
16 | }, [])
17 |
18 | if (!selectedOrderId) {
19 | return
20 | }
21 | const handleAddShippingFee = (e) => {
22 | if(e.target.value !== 0 || e.target.value !== null || e.target.value !== undefined) {
23 | setShippingFee(e.target.value)
24 | updateShippingFee(e.target.value)
25 | sumProductsInOrder()
26 | }
27 | }
28 |
29 | const handleProccessOrder = () => {
30 | proccessOrder(selectedOrder, selectedOrderId)
31 | }
32 |
33 |
34 |
35 | const hasOrder = () => {
36 | return order.name.products.map((product, productIndex) => {
37 | return
38 | })
39 | }
40 |
41 |
42 | return (
43 |
44 |
45 |
46 |
47 | { order && hasOrder() }
48 |
49 |
58 |
59 |
60 |
61 |
62 |
63 | )
64 | }
65 |
66 | const mapStateToProps = (state) => ({
67 | selectedOrder: state.orders.selectedOrder,
68 | selectedOrderId: state.orders.selectedOrderId
69 | })
70 |
71 | const mapDispatchToProps = { sumProductsInOrder, updateShippingFee, proccessOrder }
72 | export default connect(mapStateToProps, mapDispatchToProps)(ViewOrder)
--------------------------------------------------------------------------------
/src/components/Product/AddProducts.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { db } from '../../config/firebase'
3 |
4 | export default class AddProducts extends Component {
5 | state = {
6 | category: '',
7 | description: '',
8 | images: [
9 | {
10 | status: true,
11 | url : 'https://picsum.photos/600/400'
12 | },
13 | {
14 | status: true,
15 | url : 'https://picsum.photos/600/400'
16 | },
17 | {
18 | status: true,
19 | url : 'https://picsum.photos/600/400'
20 | }
21 | ]
22 | }
23 | handleChangeValue = e => {
24 | e.preventDefault()
25 | this.setState({
26 | [e.target.name] : e.target.value
27 | })
28 | }
29 |
30 | handleAddProducts = e => {
31 | db.collection("users").add({
32 | first: "Johnrel",
33 | last: "Limpag",
34 | phone: '090909',
35 | role: 'user',
36 | username: 'asdf',
37 | pass:'asdf'
38 | })
39 | .then(function(docRef) {
40 | console.log("Document written with ID: ", docRef.id);
41 | })
42 | .catch(function(error) {
43 | console.error("Error adding document: ", error);
44 | });
45 |
46 | }
47 |
48 | componentDidMount() {
49 | db.collection('set')
50 | .get()
51 | .then( snapshot => {
52 | const item = [];
53 | snapshot.forEach( doc => {
54 | let data = doc.data()
55 | item.push(data)
56 | })
57 | })
58 | .catch( error => console.log( error ))
59 | }
60 |
61 | render() {
62 | return (
63 |
87 | )
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/Product/Product.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect} from 'react'
2 | import { WishListContext } from './../../contexts/WishListContext'
3 | import { Modal } from 'react-bootstrap'
4 | import notification from './../Helpers/functions'
5 | function Product({data}) {
6 | const product = data.data;
7 | const {wishListState, wishListDispatch } = useContext(WishListContext)
8 |
9 | useEffect(()=> {
10 | if(wishListState.products.length > 0) {
11 | // localStorage.setItem('wish-list', JSON.stringify(wishListState.products))
12 | }
13 | }, [])
14 |
15 |
16 | const handleClickWish = source => {
17 | const data = source.data;
18 | let stopMapping = false,
19 | pVariation = null,
20 | pOption = null,
21 | pPrice = null
22 | source.data.priceOptions.map(item => {
23 | pVariation = item.variation
24 | if (stopMapping === true) {
25 | return
26 | }
27 | item.options.map(optionItem => {
28 | if (optionItem.is_available == 'Yes') {
29 | pOption = optionItem.option
30 | pPrice = optionItem.price
31 | stopMapping = true
32 | return
33 | }
34 | })
35 | });
36 | const obj = {
37 | id: source.id,
38 | name: data.productName,
39 | image:data.cover,
40 | variation: pVariation,
41 | option: pOption,
42 | price:Number(pPrice),
43 | unit:1,
44 | total: Number(pPrice)
45 | }
46 | if (obj.id && obj.name && obj.option && obj.image && obj.variation && obj.price && obj.unit && obj.total) {
47 | wishListDispatch({
48 | type:"ADD_WISH",
49 | payload: obj
50 | })
51 | notification('Wish', 'New product added to wish list!', 'success')
52 | } else {
53 | notification('Wish', 'Failed to add to wish list!', 'danger')
54 | }
55 | }
56 | return (
57 | product?.priceOptions[0]?.options.length > 0 ?
58 |
59 |
60 |

61 |
62 |
Php { product.priceOptions[0]?.options[0]?.price }
63 |
{ product.productName }
64 | {/*
handleClickWish(data)}> */}
65 |
66 |
67 |
:
68 | ''
69 | )
70 | }
71 | export default Product;
72 |
--------------------------------------------------------------------------------
/src/components/Product/Products.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState, useEffect, useContext } from 'react'
2 | import Product from './Product'
3 | import Container from 'react-bootstrap/Container'
4 | import { db } from '../../config/firebase'
5 | import Hero from './../Hero/Index'
6 | import { ProductContext } from '../../contexts/ProductContext'
7 |
8 | import "react-loader-spinner/dist/loader/css/react-spinner-loader.css"
9 | import './Sass/Style.scss'
10 |
11 | const Products = () => {
12 | const [categories, setCategories] = useState([])
13 | const [products, setProducts] = useState([])
14 | const {productState, productDispatch} = useContext(ProductContext)
15 |
16 | useEffect(()=> {
17 | getCategories();
18 | getProducts();
19 | setProducts(productState.products)
20 | }, [])
21 |
22 | const getCategories = () => {
23 | db.collection("category")
24 | .onSnapshot(snapshot => {
25 | const categories = []
26 | snapshot.forEach(doc => {
27 | const obj = {
28 | id: doc.id,
29 | name: doc.data()
30 | }
31 | categories.push(obj)
32 | })
33 | setCategories(categories)
34 | });
35 | }
36 |
37 | const getProducts = () => {
38 | db.collection("products")
39 | .where("status", "==", "available")
40 | .onSnapshot(snapshot => {
41 | const products = []
42 | snapshot.forEach(doc => {
43 | const product = {
44 | id: doc.id,
45 | data: doc.data()
46 | }
47 | products.push(product)
48 | })
49 | productDispatch({
50 | type:'ADD_PRODUCT',
51 | payload: [...products]
52 | })
53 | setProducts(products)
54 | });
55 | }
56 |
57 | const filterItem = categories.map(e => {e.name.name})
58 | return (
59 |
60 |
61 | {/*
62 | - MEN
63 | - WOMEN
64 | - KIDS
65 | - OTHERS
66 |
*/}
67 |
68 |
69 | {/*
70 | { filterItem }
71 | {
72 | products.length <= 0 ? : ''
81 | }
82 |
*/}
83 |
84 | {
85 | products.length ? products.map( product =>
) :
Product Loading...
86 | }
87 |
88 |
89 |
90 |
91 | )
92 | }
93 |
94 | export default Products
95 |
--------------------------------------------------------------------------------
/src/components/Product/Sass/Style.scss:
--------------------------------------------------------------------------------
1 | $primary-color: #F6F8FA;
2 | $secondary-color:#39A7AB;
3 | $primary-font: 'PT Sans', sans-serif;
4 | $secondary-font:'Roboto', sans-serif;
5 |
6 | .home-filter {
7 | border-bottom:1px solid #efefef;
8 | padding:20px;
9 | display: flex;
10 | justify-content: center;
11 | @media (max-width:767px) {
12 | font-size:14px;
13 | }
14 | li {
15 | margin:0 10px;
16 | }
17 | }
18 |
19 | .home-filter-categories {
20 | display: flex;
21 | max-width: 900px;
22 | margin:20px auto;
23 | justify-content: center;
24 | li {
25 | color: #333;
26 | text-transform: uppercase;
27 | font-size:18px;
28 | font-family: $secondary-font;
29 | font-weight: 300;
30 | @media (max-width:767px) {
31 | font-size:14px;
32 | }
33 | &:focus {
34 | color:$secondary-color;
35 | }
36 | &:not(:last-child) {
37 | margin-right:20px;
38 | }
39 | }
40 |
41 | .loader-spin {
42 | margin:0 auto;
43 | }
44 | }
45 |
46 | .product-list {
47 | display: flex;
48 | flex-wrap:wrap;
49 | justify-content: space-between;
50 | margin:0 auto 40px;
51 | padding:0 15px;
52 | width:100%;
53 | .card {
54 | width: calc(25% - 15px);
55 | margin-bottom:25px;
56 | background:#fff;
57 | display: block;
58 | border-radius: 2px;
59 | position: relative;
60 | overflow: hidden;
61 | border:0;
62 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
63 | @media (max-width:1199px) {
64 | width:calc(33.3% - 10px);
65 | margin-bottom: 15px;
66 | }
67 | @media (max-width:991px) {
68 | width:calc(50% - 10px);
69 | margin-bottom: 15px;
70 | }
71 | &:last-child {
72 | background:transparent;
73 | box-shadow: none;
74 | }
75 | }
76 | .card-wrapper {
77 | padding:0;
78 | }
79 | .card-img {
80 | width:100%;
81 | border-radius: 0;
82 | }
83 | .card-body {
84 | padding:15px 50px 15px 15px;
85 | }
86 | .card-price {
87 | font-weight: 400;
88 | font-family: $primary-font;
89 | font-size:15px;
90 | margin-bottom: 5px;
91 | color:$secondary-color;
92 | @media (max-width:767px) {
93 | font-size:14px;
94 | }
95 | }
96 | .card-title {
97 | color:#666;
98 | font-family: $secondary-font;
99 | font-size:14px;
100 | font-weight: 300;
101 | margin:0;
102 | @media (max-width:767px) {
103 | font-size:12px;
104 | }
105 | }
106 | .card-cart {
107 | position: absolute;
108 | bottom:0;
109 | right:0;
110 | width:45px;
111 | height:60px;
112 | display: flex;
113 | align-items: center;
114 | justify-content: center;
115 | }
116 | ion-icon {
117 | font-size:20px;
118 | color:$secondary-color;
119 | }
120 | }
--------------------------------------------------------------------------------
/src/components/Segments/Add.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { Button, Form, Modal } from 'react-bootstrap'
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4 | import { faFile, faSave, faTimes, faPlus } from '@fortawesome/free-solid-svg-icons'
5 |
6 | export default class Add extends Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | segments: '',
11 | show:false,
12 | }
13 | }
14 |
15 | handleUpdateStateSegments = e => {
16 | e.preventDefault()
17 | this.setState({
18 | segments: e.target.value
19 | })
20 | }
21 | handleFormSubmit = e => {
22 | e.preventDefault()
23 | this.props.handleAddNewSegments(this.state.segments)
24 | this.setState({
25 | segments:''
26 | })
27 |
28 | }
29 | handleClose = () => {
30 | this.setState({
31 | show:false
32 | })
33 | }
34 | handleShow = () => {
35 | this.setState({
36 | show:true
37 | })
38 | }
39 | render() {
40 | return (
41 |
42 |
45 |
51 |
69 |
70 |
71 |
72 |
73 | )
74 | }
75 | }
76 |
77 | const addButton = {
78 | marginTop:'10px',
79 | float:'right',
80 | backgroundColor:'#00807d',
81 | borderColor:'#00807d'
82 | }
--------------------------------------------------------------------------------
/src/components/Segments/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { ListGroup, Card } from 'react-bootstrap'
3 | import Container from 'react-bootstrap/Container'
4 | import List from './List'
5 | import Add from './Add'
6 | import { db } from '../../config/firebase'
7 | import Alert from '../Alert/Index'
8 | import Hero from './../Hero/Index'
9 | import { AuthContext } from './../../contexts/AuthContext'
10 | import { Redirect } from 'react-router-dom'
11 |
12 | const container = {
13 | padding:30,
14 | paddingTop:0
15 | }
16 | export default class Index extends Component {
17 | state = {
18 | segments: [],
19 | alertMessage: null,
20 | showAlert: false,
21 | alertType: 'success'
22 | }
23 | alertTimeout = null
24 |
25 | componentDidMount() {
26 | db.collection("segments")
27 | .onSnapshot(snapshot => {
28 | const segments = []
29 | snapshot.forEach(doc => {
30 | const obj = {
31 | id: doc.id,
32 | name: doc.data()
33 | }
34 | segments.push(obj)
35 | })
36 | this.setState({ segments })
37 | });
38 | this.setState({ isLoading: true })
39 | db.collection("segments")
40 | .onSnapshot(snapshot => {
41 | const segmentsList = []
42 | snapshot.forEach(doc => {
43 | const dataObj = {
44 | id: doc.id,
45 | name: doc.data()
46 | }
47 | segmentsList.push(dataObj)
48 |
49 | })
50 | this.setState({
51 | segmentsList,
52 | isLoading: false
53 | })
54 | })
55 | }
56 | handleAddNewSegments = (newSegments) => {
57 | if (newSegments.length > 2) {
58 | db.collection("segments").add({
59 | name: newSegments
60 | })
61 | .then(function (docRef) {
62 | console.log("Document written with ID: ", docRef.id);
63 | })
64 | .catch(function (error) {
65 | console.error("Error adding document: ", error);
66 | });
67 | const alertObj = {
68 | type: 'success',
69 | message: 'New Segments Added Successfuly!'
70 | }
71 | if (this.alertTimeout) {
72 | clearTimeout(this.alertTimeout)
73 | this.setState({
74 | showAlert: false
75 | }, () => {
76 | this.alertTimeout = setTimeout(() => {
77 | this.handleShowAlert(alertObj)
78 | }, 250)
79 | })
80 | } else {
81 | this.handleShowAlert(alertObj)
82 | }
83 | } else {
84 | const alertObj = {
85 | type: 'failed',
86 | message: 'Character must be greater than 2.'
87 | }
88 | if (this.alertTimeout) {
89 | clearTimeout(this.alertTimeout)
90 | this.setState({
91 | showAlert: false
92 | }, () => {
93 | this.alertTimeout = setTimeout(() => {
94 | this.handleShowAlert(alertObj)
95 | }, 250)
96 | })
97 | } else {
98 | this.handleShowAlert(alertObj)
99 | }
100 | }
101 | }
102 | handleShowAlert = ({ message, type }) => {
103 | this.setState({
104 | showAlert: true,
105 | alertMessage: message,
106 | alertType: type
107 | }, () => {
108 | this.alertTimeout = setTimeout(() => {
109 | this.setState({
110 | showAlert: false,
111 | })
112 | }, 3000)
113 | })
114 | }
115 | handleRemoveSegments = (id) => {
116 | db.collection("segments").doc(id).delete().then(function () {
117 | console.log('deleted')
118 | }).catch(function (error) {
119 | console.error("Error removing document: ", error);
120 | });
121 | const alertObj = {
122 | type: 'success',
123 | message: 'Segment Deleted Successfuly!'
124 | }
125 | if (this.alertTimeout) {
126 | clearTimeout(this.alertTimeout)
127 | this.setState({
128 | showAlert: false
129 | }, () => {
130 | this.alertTimeout = setTimeout(() => {
131 | this.handleShowAlert(alertObj)
132 | }, 250)
133 | })
134 | } else {
135 | this.handleShowAlert(alertObj)
136 | }
137 | }
138 |
139 | render() {
140 | const isLoading = this.state.isLoading ?
141 | (Loading segments...
) :
142 | (
146 | )
147 |
148 | return (
149 |
150 |
151 | {(value) => {
152 | if(Object.keys(value.state.user).length === 0 || value.state.user === null) {
153 | return
154 | }
155 | }}
156 |
157 |
158 |
159 |
164 |
165 |
166 |
167 | List of segments
168 |
169 |
170 |
171 | {isLoading}
172 |
173 |
176 |
177 |
178 |
179 |
180 |
181 | )
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/components/Segments/List.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, Component } from 'react'
2 | import { Modal , Button, ListGroup, Badge } from 'react-bootstrap'
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
4 | import { faTrashAlt, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons'
5 | export default class List extends Component {
6 | state = {
7 | show:false,
8 | idToRemove:null
9 | }
10 |
11 | removeSegments = () => {
12 | if(this.state.idToRemove) {
13 | this.props.handleRemoveSegments(this.state.idToRemove)
14 | this.setState({
15 | show:false
16 | })
17 | }
18 | }
19 | handleClose = () => {
20 | this.setState({
21 | show:false
22 | })
23 | }
24 | handleShow(id) {
25 | this.setState({
26 | show:true,
27 | idToRemove: id
28 | })
29 | }
30 | render() {
31 | const list = this.props.segmentsList.map((doc, index) => {
32 | return ( {index + 1} {doc.name.name}
33 | this.handleShow(doc.id)}
34 | style={{float:'right', cursor:'pointer'}}/>
35 |
36 | )
37 | })
38 | return (
39 |
40 | { list != '' ? list : 'No size at this moment' }
41 |
47 |
48 | Delete Segment
49 |
50 |
51 |
52 | Are you sure you want to delete?
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | )
62 | }
63 | }
64 |
65 |
66 | const removeButton = {
67 | backgroundColor:'#f53d2d',
68 | borderColor:'#f53d2d'
69 | }
--------------------------------------------------------------------------------
/src/components/Update/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import Container from 'react-bootstrap/Container'
3 | import Product from './Product'
4 | import { Redirect } from 'react-router-dom'
5 | import { db } from '../../config/firebase'
6 | import './Sass/Index.scss'
7 | import { AuthContext } from './../../contexts/AuthContext'
8 |
9 | export default class Index extends Component {
10 |
11 | state = {
12 | products:[]
13 | }
14 | _isMounted = false
15 |
16 | componentDidMount() {
17 | this._isMounted = true
18 | this.getProducts();
19 | }
20 | componentWillUnmount() {
21 | this._isMounted = false
22 | }
23 | getProducts() {
24 | db.collection("products")
25 | .onSnapshot(snapshot => {
26 | const products = []
27 | snapshot.forEach(doc => {
28 | const product = {
29 | id: doc.id,
30 | data: doc.data()
31 | }
32 | products.push(product)
33 | })
34 | if ( this._isMounted ) {
35 | this.setState({ products })
36 | }
37 | });
38 | }
39 |
40 | render() {
41 | return (
42 |
43 |
44 | {(value) => {
45 | if(Object.keys(value.state.user).length === 0 || value.state.user === null) {
46 | return
47 | }
48 | }}
49 |
50 |
51 |
52 | Edit Products
53 |
54 |
55 |
56 |
57 |
58 | {
59 | this.state.products ? this.state.products.map( product =>
) : ''
60 | }
61 |
62 |
63 |
64 |
65 | )
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/Update/Product.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, Component} from 'react'
2 | import Switch from 'react-input-switch'
3 | import { db } from '../../config/firebase'
4 | import { Link } from 'react-router-dom'
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6 | import { faCog } from '@fortawesome/free-solid-svg-icons'
7 |
8 | export default class Product extends Component {
9 | state = {
10 | value : this.props.data.data.status === "available" ? 1 : 0
11 | }
12 | handleOnOff = (id, status)=> {
13 | this.setState({
14 | value : this.state.value === 0 ? 1 : 0
15 | })
16 | db.collection('products').doc(id).update({
17 | status: status == 'available' ? 'not available' : 'available'
18 | })
19 | }
20 | render() {
21 | const data = this.props.data
22 | const product = data.data
23 | return (
24 |
25 | {product.priceOptions[0].options.length > 0 ?
26 |
27 |
28 |
29 |

30 |
31 |
32 |
{ product.productName }
33 |
Php { product.priceOptions[0].options[0].price }
34 |
35 |
36 | this.handleOnOff(data.id, data.data.status)}
39 | styles={{
40 | track: {
41 | backgroundColor: '#efefef'
42 | },
43 | trackChecked: {
44 | backgroundColor: '#39A7AB'
45 | },
46 | button: {
47 | backgroundColor: '#39A7AB'
48 | },
49 | buttonChecked: {
50 | backgroundColor: '#fff'
51 | }
52 | }}
53 | />
54 |
55 |
56 |
57 |
58 |
59 |
:
60 | ''
61 | }
62 |
63 | )
64 | }
65 | }
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/components/Update/Sass/Index.scss:
--------------------------------------------------------------------------------
1 |
2 | @import './../../../styles/variables.scss';
3 |
4 | .UPDATE-product {
5 | padding:15px;
6 | padding-top:0;
7 | }
8 |
9 | .UPDATE-product-list {
10 | display: flex;
11 | justify-content: space-between;
12 | flex-wrap: wrap;
13 | }
14 |
15 | .UPDATE-product-list .card {
16 | margin-bottom:12px;
17 | border-radius: 6px !important;
18 | overflow: hidden;
19 | border:0;
20 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
21 | transition: 0.3s;
22 | max-width: 32%;
23 | width:100%;
24 | @media (max-width:1199px) {
25 | max-width: 48.5%;
26 | }
27 | &:hover {
28 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0, 0, 0, 0.06);
29 | }
30 | .left {
31 | min-height: 92px;
32 | }
33 | .card-img {
34 | border-radius: 0;
35 | }
36 | .card-price {
37 | font-weight: 400;
38 | font-family: $primary-font;
39 | font-size:14px;
40 | margin-top: 5px;
41 | color:$secondary-color;
42 | @media (max-width:767px) {
43 | font-size:14px;
44 | }
45 | }
46 | .card-title {
47 | color:#666;
48 | font-family: $secondary-font;
49 | font-size:16px;
50 | font-weight: 300;
51 | margin:0;
52 | }
53 | .card-wrapper {
54 | display: flex;
55 | align-items: center;
56 | position: relative;
57 | }
58 | .left {
59 | max-width:80px;
60 | }
61 | .center {
62 | padding-left: 15px;
63 | }
64 | .right {
65 | display: flex;
66 | justify-content: flex-end;
67 | align-items: center;
68 | float:right;
69 | position: absolute;
70 | right:20px;
71 | top:0;
72 | height: 100%;
73 | a {
74 | color: $secondary-color;
75 | }
76 | label {
77 | margin-right:10px;
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/src/components/Uploads/AddVariationOptions.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Button, FormControl } from 'react-bootstrap'
3 | import { FontAwesomeIcon} from '@fortawesome/react-fontawesome'
4 | import { faTrash } from '@fortawesome/free-solid-svg-icons'
5 |
6 |
7 | export default function AddVariationOptions(props) {
8 | return (
9 |
10 | Fields of options:
11 |
12 | {props.variationOptions.map((option, index) => - props.handleAddVariationOptionValue(e, index) }/>
13 | props.handleRemoveVariationOption(index) }/>
14 |
)}
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Uploads/AddVariations.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Button, FormControl } from 'react-bootstrap'
3 | import { FontAwesomeIcon} from '@fortawesome/react-fontawesome'
4 | import { faTrash } from '@fortawesome/free-solid-svg-icons'
5 |
6 |
7 | export default function AddVariations(props) {
8 | return (
9 |
10 | Fields of variations:
11 |
12 | {props.variations.map((variation, index) => - props.handleAddVariationValue(e, index) }/>
13 | props.handleRemoveVariation(index) }/>
14 |
)}
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/Uploads/CategoryDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Form } from 'react-bootstrap'
3 |
4 | export default function CategoryDropdown(props) {
5 | const categories = props.categories
6 | const populateDropdownCategory = (categories) => {
7 | if (categories) {
8 | return categories.map(category => )
9 | }
10 | }
11 | const addSelectedCategory = (category) => {
12 | props.handleAddSectedCategoryToState(category)
13 | }
14 | return (
15 |
16 |
17 | Select Category:
18 | addSelectedCategory(e.target.value) } as="select">
19 |
20 | { categories ? populateDropdownCategory(categories) : ( ) }
21 |
22 |
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Uploads/Index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react'
2 | import { ProgressBar, Form, Button, Card } from 'react-bootstrap'
3 | import './scss/index.scss'
4 | import firebase from 'firebase/app'
5 | import { db, storage } from '../../config/firebase'
6 | import FileUploader from "react-firebase-file-uploader"
7 | import uuid from 'react-uuid'
8 | import Container from 'react-bootstrap/Container'
9 | import CategoryDropdown from './CategoryDropdown'
10 | import SegmentsDropdown from './SegmentsDropdown'
11 | import AddVariations from './AddVariations'
12 | import AddVariationOptions from './AddVariationOptions'
13 | import PriceSettings from './PriceSettings'
14 | import Alert from '../Alert/Index'
15 | import { Redirect } from 'react-router-dom'
16 | import { AuthContext } from './../../contexts/AuthContext'
17 |
18 | const container = {
19 | maxWidth: '600px',
20 | margin: '60px auto',
21 | float:'none'
22 | }
23 | const imageUpload = {
24 | backgroundColor: '#39A7AB',
25 | color: 'white',
26 | padding: 10,
27 | borderRadius: 4,
28 | cursor: 'pointer',
29 | display:'flex',
30 | alignItems:'center',
31 | marginBottom:'15px',
32 | justifyContent:'center'
33 | }
34 | export default class Index extends Component {
35 |
36 | state = {
37 | categories: [],
38 | segments: [],
39 | alertMessage: null,
40 | showAlert: false,
41 | alertType: 'success',
42 | filenames: [],
43 | downloadURLs: [],
44 | isUploading: false,
45 | uploadProgress: 0,
46 | variationsValue: [],
47 | variations: [],
48 | variationOptionValue: [],
49 | variationOptions: [],
50 | priceOptions:[],
51 | productName:'',
52 | selectedSegment:null,
53 | selectedCategory:null,
54 | productDescriptions:null,
55 | cover:''
56 | }
57 | alertTimeout = null
58 |
59 |
60 | componentDidMount() {
61 | this.getCategories()
62 | this.getSegments()
63 |
64 | }
65 | componentWillUnmount() {
66 | }
67 |
68 | handleShowAlert = ({ message, type }) => {
69 | this.setState({
70 | showAlert: true,
71 | alertMessage: message,
72 | alertType: type
73 | }, () => {
74 | this.alertTimeout = setTimeout(() => {
75 | this.setState({
76 | showAlert: false,
77 | })
78 | }, 3000)
79 | })
80 | }
81 | handleAlertMessage = (status, message) => {
82 | const alertObj = {
83 | type: status,
84 | message
85 | }
86 | if (this.alertTimeout) {
87 | clearTimeout(this.alertTimeout)
88 | this.setState({
89 | showAlert: false
90 | }, () => {
91 | this.alertTimeout = setTimeout(() => {
92 | this.handleShowAlert(alertObj)
93 | }, 250)
94 | })
95 | } else {
96 | this.handleShowAlert(alertObj)
97 | }
98 | }
99 |
100 | handleUploadStart = () => this.setState({
101 | isUploading: true,
102 | uploadProgress: 0
103 | });
104 |
105 | handleProgress = progress => this.setState({
106 | uploadProgress: progress
107 | });
108 |
109 | handleUploadError = error => {
110 | this.setState({
111 | isUploading: false
112 | // Todo: handle error
113 | });
114 | console.error(error);
115 | };
116 |
117 | handleUploadSuccess = async filename => {
118 | const downloadURL = await firebase
119 | .storage()
120 | .ref("images")
121 | .child(filename)
122 | .getDownloadURL();
123 |
124 | this.setState(oldState => ({
125 | filenames: [...oldState.filenames, filename],
126 | downloadURLs: [...oldState.downloadURLs, downloadURL],
127 | uploadProgress: 100,
128 | isUploading: false
129 | }));
130 | };
131 |
132 | getCategories = () => {
133 | db.collection("category")
134 | .onSnapshot(snapshot => {
135 | const categories = []
136 | snapshot.forEach(doc => {
137 | const obj = {
138 | id: doc.id,
139 | name: doc.data()
140 | }
141 | categories.push(obj)
142 | })
143 | this.setState({ categories })
144 | });
145 | }
146 |
147 | getSegments = () => {
148 | db.collection("segments")
149 | .onSnapshot(snapshot => {
150 | const segments = []
151 | snapshot.forEach(doc => {
152 | const obj = {
153 | id: doc.id,
154 | name: doc.data()
155 | }
156 | segments.push(obj)
157 | })
158 | this.setState({ segments })
159 | });
160 | }
161 |
162 | handleRemoveImage = i => {
163 | let filenames = [...this.state.filenames];
164 | let downloadURLs = [...this.state.downloadURLs]
165 | const imgRef = storage.ref('images/'+ this.state.filenames[i]);
166 | // Delete the file
167 | imgRef.delete().then(() => {
168 | console.log('image deleted success')
169 | filenames.splice(i, 1)
170 | downloadURLs.splice(i, 1)
171 | this.setState({
172 | filenames,
173 | downloadURLs,
174 | cover: ''
175 | })
176 |
177 | }).catch( error => {
178 | console.log(error)
179 | });
180 | }
181 |
182 | handleAddVariations = e => {
183 | const variation = 'variation-' + uuid()
184 | this.setState(prevState => ({
185 | variations: prevState.variations.concat([variation]),
186 | variationOptions: [],
187 | variationOptionValue:[]
188 | }));
189 | }
190 |
191 | handleRemoveVariation = index => {
192 | const variationsValue = [...this.state.variationsValue]
193 | variationsValue.splice(index, 1)
194 | const variations = [...this.state.variations]
195 | variations.splice(index, 1)
196 | const priceOptions = [...this.state.priceOptions]
197 | priceOptions.splice(index, 1)
198 | this.setState({
199 | variationsValue,
200 | variations,
201 | priceOptions
202 | })
203 | }
204 |
205 | handleAddVariationValue = (e, index) => {
206 | const value = e.target.value
207 | const variationsValue = [...this.state.variationsValue],
208 | priceOptions = [...this.state.priceOptions]
209 | variationsValue[index] = value
210 | priceOptions[index] = { variation: value, options : [{}] }
211 | this.setState({
212 | variationsValue,
213 | priceOptions
214 | })
215 | }
216 |
217 | handleAddVariationOption = e => {
218 | const option = 'variation-option-' + uuid()
219 | this.setState(prevState => ({
220 | variationOptions: prevState.variationOptions.concat([option])
221 | }));
222 | }
223 |
224 | handleRemoveVariationOption = index => {
225 | const variationOptionValue = [...this.state.variationOptionValue]
226 | variationOptionValue.splice(index, 1)
227 | const variationOptions = [...this.state.variationOptions]
228 | variationOptions.splice(index, 1)
229 | const priceOptions = [...this.state.priceOptions]
230 | priceOptions.map(item => item.options.splice(index, 1))
231 | this.setState({
232 | variationOptionValue,
233 | variationOptions,
234 | priceOptions
235 | })
236 | }
237 |
238 | handleAddVariationOptionValue = (e, index) => {
239 | const value = e.target.value
240 | const variationOptionValue = [...this.state.variationOptionValue],
241 | priceOptions = [...this.state.priceOptions]
242 | variationOptionValue[index] = value
243 |
244 | priceOptions.map(item => {
245 | item.options[index] = {
246 | option: value,
247 | price: null,
248 | is_available: 'Yes'
249 | }
250 | })
251 | this.setState({
252 | variationOptionValue,
253 | priceOptions
254 | })
255 | }
256 |
257 | handleSetPrice = (variationIndex, optionIndex, value) => {
258 | const priceOptions = [...this.state.priceOptions]
259 | priceOptions[variationIndex].options[optionIndex].price = value
260 | this.setState({
261 | priceOptions
262 | })
263 | }
264 |
265 | handleSetAvailability = (variationIndex, optionIndex, value) => {
266 | const priceOptions = [...this.state.priceOptions]
267 | priceOptions[variationIndex].options[optionIndex].is_available = value
268 | this.setState({
269 | priceOptions
270 | })
271 | }
272 |
273 | handleAddNameToState = (name) => {
274 | this.setState({
275 | productName: name
276 | })
277 | }
278 |
279 | handleAddSectedSegmentToState = (segment) => {
280 | this.setState({
281 | selectedSegment: segment
282 | })
283 | }
284 |
285 | handleAddSectedCategoryToState = (category) => {
286 | this.setState({
287 | selectedCategory: category
288 | })
289 | }
290 |
291 | handleSetCover = (url) => {
292 | //console.log(this.cardRef.current.class)
293 | this.setState({
294 | cover: url
295 | })
296 | }
297 |
298 | handleAddDescriptionsToState = (descriptions) => {
299 | this.setState({
300 | productDescriptions: descriptions
301 | })
302 | }
303 |
304 | handleProductEntry = () => {
305 | const product = {
306 | productName : this.state.productName,
307 | segment : this.state.selectedSegment,
308 | category: this.state.selectedCategory,
309 | descriptions: this.state.productDescriptions,
310 | productImages : this.state.downloadURLs,
311 | priceOptions : this.state.priceOptions,
312 | cover : this.state.cover,
313 | status : 'available',
314 | timestamp: Math.floor(Date.now() / 1000)
315 | }
316 |
317 | if (product.cover == '') {
318 | this.handleAlertMessage('failed', 'Please select product cover!')
319 | return;
320 | }
321 |
322 | if (!product.productName) {
323 | this.handleAlertMessage('failed', 'Please enter product name!')
324 | return;
325 | }
326 |
327 | if (!product.segment) {
328 | this.handleAlertMessage('failed', 'Please select segment!')
329 | return;
330 | }
331 |
332 | if (!product.category) {
333 | this.handleAlertMessage('failed', 'Please select category!')
334 | return;
335 | }
336 |
337 | if (product.productImages.length < 1) {
338 | this.handleAlertMessage('failed', 'Please add atleast one image!')
339 | return;
340 | }
341 |
342 | if (product.priceOptions.length < 1) {
343 | this.handleAlertMessage('failed', 'Please set variation!')
344 | return;
345 | }
346 |
347 | if (product.priceOptions.length > 0) {
348 | let isPrice = true;
349 | if (JSON.stringify(product.priceOptions[0].options[0]) === '{}') {
350 | this.handleAlertMessage('failed', 'Please set price option!')
351 | return;
352 | }
353 | product.priceOptions.map( variation => {
354 | variation.options.map(option => {
355 | if (option.price == null) {
356 | isPrice = false
357 | }
358 | })
359 | })
360 | if ( !isPrice ) {
361 | this.handleAlertMessage('failed', 'Please set all option price!')
362 | return;
363 | }
364 | }
365 | //add products
366 | db.collection("products").add(product)
367 | .then( docRef => {
368 | this.handleAlertMessage('success', 'New product has been added!')
369 | this.setState({
370 | filenames: [],
371 | downloadURLs: [],
372 | isUploading: false,
373 | uploadProgress: 0,
374 | variationsValue: [],
375 | variations: [],
376 | variationOptionValue: [],
377 | variationOptions: [],
378 | priceOptions:[],
379 | productName:null,
380 | productDescriptions:null,
381 | cover:''
382 | })
383 | })
384 | .catch(error => {
385 | this.handleAlertMessage('failed', 'Failed to add product!')
386 | console.error("Error adding document: ", error);
387 | });
388 | }
389 |
390 | render() {
391 | const setPrice = this.state.variationsValue.map( (variation,index) => {
392 | let options = []
393 | this.state.variationOptionValue.map((option, optionIndex) => {
394 | const obj = {
395 | option,
396 | optionIndex
397 | }
398 | options.push(obj)
399 | })
400 | return
408 | })
409 |
410 | return (
411 |
412 |
413 |
414 | {(value) => {
415 | if(Object.keys(value.state.user).length === 0 || value.state.user === null) {
416 | return
417 | }
418 | }}
419 |
420 |
421 |
422 | Add New Product
423 |
424 |
425 |
426 |
427 |
432 | {/*
433 |
434 | */}
435 |
436 |
437 | Add Form
438 |
439 |
440 |
455 | Filenames: {this.state.filenames.join(", ")}
456 |
457 |
458 | {this.state.downloadURLs.map((downloadURL, i) => {
459 | return (
460 |
this.handleSetCover(downloadURL)}>
461 |

462 |
463 |
464 | )
465 | })}
466 |
467 |
468 |
470 | Product Name:
471 | this.handleAddNameToState(e.target.value) } value={this.state.productName || ''} type="text" />
472 |
473 |
474 |
475 |
476 | Descriptions:
477 | this.handleAddDescriptionsToState(e.target.value) } value={this.state.productDescriptions || ''} as="textarea" rows="3" />
478 |
479 |
480 |
481 |
487 |
488 |
494 | { setPrice ? (Set the price below:
) : ''}
495 | { setPrice }
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 | )
506 | }
507 | }
--------------------------------------------------------------------------------
/src/components/Uploads/PriceSettings.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment} from 'react'
2 | import {FormControl, Form} from 'react-bootstrap'
3 |
4 | export default function PriceSettings ({variation, variationIndex, variationOptions, setPrice, setAvailability}) {
5 | let mapOptions = null
6 | const handleSetPrice = (variationIndex, index, value) => {
7 | setPrice(variationIndex, index, value)
8 | }
9 | const handleSetAvailability = (variationIndex, index, value) => {
10 | setAvailability(variationIndex, index, value)
11 | }
12 | if (variationOptions) {
13 | mapOptions = variationOptions.map( (o,i) => {
14 | return (
15 |
16 | - { o.option }
17 | - handleSetPrice(variationIndex, o.optionIndex, e.target.value)}>
18 | -
19 | handleSetAvailability(variationIndex, o.optionIndex, e.target.value)}>
20 |
21 |
22 |
23 |
24 |
25 | )
26 | })
27 | }
28 | return (
29 |
30 |
31 |
{variation}
32 |
33 |
34 | - Option
35 | - Selling Price
36 | - Availabity
37 |
38 | { mapOptions }
39 |
40 |
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/Uploads/SegmentsDropdown.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import { Form } from 'react-bootstrap'
3 | export default function SegmentDropdown(props) {
4 | const segments = props.segments
5 | const populateDropdownSegment = (segments) => {
6 | if (segments) {
7 | return segments.map(segment => )
8 | }
9 | }
10 | const addSelectedSegment = (segment) => {
11 | props.handleAddSectedSegmentToState(segment)
12 | }
13 | return (
14 |
15 |
16 | Select Segments:
17 | addSelectedSegment(e.target.value)} as="select">
18 |
19 | {segments ? populateDropdownSegment(segments) : ()}
20 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Uploads/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import './../../../styles/variables.scss';
2 |
3 | .cards {
4 | display: flex;
5 | justify-content: space-between;
6 | flex-wrap: wrap;
7 | .card {
8 | width:48%;
9 | margin-bottom:25px;
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | position: relative;
14 | &.card-cover {
15 | &:before {
16 | content:'Cover';
17 | width:100%;
18 | height:100%;
19 | position: absolute;
20 | left:0;
21 | right:0;
22 | z-index: 1;
23 | background:rgba($secondary-color, 0.4);
24 | color:#fff;
25 | display: flex;
26 | font-size:18px;
27 | font-family: $secondary-font;
28 | font-weight: 100;
29 | align-items: center;
30 | justify-content: center;
31 | }
32 | }
33 | }
34 | Button.card-close {
35 | width:30px;
36 | height:30px;
37 | border-radius:0;
38 | position: absolute;
39 | top:0;
40 | right:0;
41 | padding:0;
42 | font-size:14px;
43 | z-index: 2;
44 | }
45 | }
46 |
47 | // variations
48 | .variation-list {
49 | padding:0;
50 | li {
51 | display: flex;
52 | align-items: center;
53 | list-style-type: none;
54 | margin:0 0 10px;
55 | input {
56 | margin-right: 8px;
57 | }
58 | svg {
59 | cursor: pointer;
60 | }
61 | }
62 | }
63 |
64 | // Price Settings
65 | .each-variation {
66 | li {
67 | list-style-type: none;
68 | border:1px solid #efefef;
69 | flex:1;
70 | &:not(:last-child) {
71 | border-right:0;
72 | }
73 | }
74 | .each-variation-table-items {
75 | display: flex;
76 | }
77 | }
78 |
79 | // container
80 | .add-entry-products-container {
81 | margin:30px auto !important;
82 | .btn-primary {
83 | background:$secondary-color;
84 | border:1px solid $secondary-color;
85 | display: flex;
86 | align-items:center;
87 | justify-content: center;
88 | width:100%;
89 | &:focus {
90 | border:0;
91 | outline:0;
92 | }
93 | }
94 | .variation-title,
95 | .option-title,
96 | .price-title {
97 | @include font-size(15px, 16px, 500px, 1280px);
98 | font-family: $secondary-font;
99 | margin-bottom:8px;
100 | }
101 | .option-title,
102 | .price-title {
103 | margin-top: 10px;
104 | }
105 | // set price
106 | .each-variation {
107 | margin-bottom:15px;
108 | }
109 | .each-variation-title {
110 | text-transform: uppercase;
111 | margin-bottom: 10px;
112 | display: block;
113 | }
114 | .each-variation-table-items {
115 | li {
116 | padding:6px;
117 | display: flex;
118 | align-items: center;
119 | justify-content: center;
120 | text-align: center;
121 | }
122 | }
123 |
124 | .btn-product-save {
125 | border-color:#f53d2d;
126 | background:#f53d2d;
127 | }
128 | }
129 |
130 | // form
131 | .add-entry-form {
132 | @include font-size(15px, 16px, 500px, 1280px);
133 | font-family: $secondary-font;
134 | .form-control {
135 | @include font-size(15px, 16px, 500px, 1280px);
136 | }
137 | .form-label {
138 | margin-bottom:6px;
139 | font-family: $secondary-font;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/components/WishList/Index.js:
--------------------------------------------------------------------------------
1 | import React, {Fragment, useContext, useState, useEffect } from 'react'
2 | import Container from 'react-bootstrap/Container'
3 | import { WishListContext } from '../../contexts/WishListContext'
4 | import { Link } from 'react-router-dom'
5 | import Product from './Product'
6 | import firebase from '../../config/firebase'
7 | import notification from './../Helpers/functions'
8 |
9 | export default function Index() {
10 | const [wish, setWish] = useState([])
11 | const [total, setTotal] = useState(0)
12 | const [currentUserName, setCurrentUserName] = useState(null)
13 | const [currentUserPhone, setCurrentUserPhone] = useState(null)
14 | const [currentUserAddress, setCurrentUserAddress] = useState(null)
15 | const [useCurUserInfo, setUseCurUserInfo] = useState(false)
16 | const { wishListState, wishListDispatch } = useContext(WishListContext)
17 |
18 | useEffect(()=>{
19 | setWish(wishListState.products)
20 | getTotal()
21 | }, [wishListState])
22 |
23 | const getTotal = () => {
24 | const totalResult = wishListState.products.reduce((acc, item) => item.total + acc, 0)
25 | setTotal(totalResult)
26 | }
27 |
28 | const handleOrder = () => {
29 | if ( firebase.auth().currentUser) {
30 | const userId = firebase.auth().currentUser.uid
31 | const phone = firebase.auth().currentUser.phoneNumber
32 | const userName = firebase.auth().currentUser.displayName
33 |
34 | console.log(currentUserName)
35 | console.log(currentUserAddress)
36 | console.log(currentUserPhone)
37 |
38 | } else {
39 | notification('Wish', 'You must be logged in to order. Thank you!', 'danger')
40 | }
41 | }
42 |
43 | const handleChangeName = (e) => {
44 | setCurrentUserName(e.target.value)
45 | }
46 | const handleChangeAddress = (e) => {
47 | setCurrentUserAddress(e.target.value)
48 | }
49 | const handleChangePhone = (e) => {
50 | setCurrentUserPhone(e.target.value)
51 | }
52 | const handleUseUserInfo = () => {
53 | setUseCurUserInfo(!useCurUserInfo)
54 | if( useCurUserInfo === false) {
55 | if (firebase.auth().currentUser) {
56 | setCurrentUserName(firebase.auth().currentUser.displayName)
57 | setCurrentUserPhone(firebase.auth().currentUser.phoneNumber)
58 | } else {
59 | notification('Wish', 'You must be logged in to continue. Thank you!', 'danger')
60 | }
61 | } else {
62 | setCurrentUserName('')
63 | setCurrentUserPhone('')
64 | }
65 | }
66 |
67 | return (
68 |
69 |
70 |
71 | Wish List
72 |
73 |
74 |
75 |
76 |
77 | {
78 | wish.length <= 0 ?
No wish product added! Go to products
:
79 | wish.map((product, productIndex) => {
80 | return
81 | })
82 | }
83 |
84 |
85 |
Total
86 |
87 | - Total :{total}
88 | - Shipping :Add later
89 |
90 |
91 |
92 |
Add Shipping Info
93 |
113 |
114 |
115 | Order Now
116 |
117 |
118 |
119 |
120 | )
121 | }
122 |
--------------------------------------------------------------------------------
/src/components/WishList/Product.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useContext, useState, useEffect} from 'react'
2 | import './Sass/Index.scss'
3 | import { WishListContext } from '../../contexts/WishListContext'
4 | import { ProductContext } from '../../contexts/ProductContext'
5 | import Popup from "reactjs-popup";
6 |
7 | const Product = ({product, id, productIndex}) => {
8 | const{name, price, image, variation, option, unit, total} = product
9 | const {wishListState, wishListDispatch} = useContext(WishListContext)
10 | const {productState} = useContext(ProductContext)
11 | const [popupVariation, setPopupVariation] = useState(false)
12 | const [popupOption, setPopupOption] = useState(false)
13 | const [listVariations, setListVariations] = useState([])
14 | const [listOptions, setListOptions] = useState([])
15 | const [selectedOption, setSelectedOption] = useState({})
16 | const [updateToggle, setUpdateToggle] = useState(false)
17 |
18 | useEffect(()=> {
19 | generatePopupVariation()
20 | generatePopupOptions()
21 | }, [productState])
22 |
23 | const updateLocalStorage = (products) => {
24 | if (products.length > 0) {
25 | wishListDispatch({
26 | type:"REPLACE_WISH",
27 | payload: products.map(item => item)
28 | })
29 | }
30 | }
31 |
32 | const generatePopupOptions = () => {
33 | const products = productState.products
34 | if (products.length > 0) {
35 | const finedProduct = products.find(item => item.id == id)
36 | const priceOptions = finedProduct.data.priceOptions
37 | setListOptions(priceOptions[0].options)
38 | }
39 | }
40 |
41 | const generatePopupOptionContent= () => {
42 | if ( listOptions.length > 0 ) {
43 | const filtered = listOptions.filter(item => item.is_available !== 'No')
44 | return filtered.map((item, index) => {item.option}Price: {item.price} changeOption(index, item.option, item.price)} >Select
)
45 | }
46 | }
47 |
48 | const changeOption = (index, option, price) => {
49 | const action = {
50 | id,
51 | option,
52 | price
53 | }
54 | if (option) {
55 | wishListDispatch({
56 | type:'UPDATE_OPTION_WISH',
57 | action
58 | })
59 | updateLocalStorage(wishListState.products)
60 | modalOption('close')
61 | }
62 | }
63 |
64 | const generatePopupVariation = () => {
65 | const products = productState.products
66 | if (products.length > 0) {
67 | const finedProduct = products.find(item => item.id == id)
68 | const priceOptions = finedProduct.data.priceOptions
69 | setListVariations(priceOptions)
70 | }
71 | }
72 |
73 | const generatePopupVariationContent = () => {
74 | if ( listVariations.length > 0 ) {
75 | return listVariations.map((item, index) => changeVariation(index, item.variation, item.options)} key={index}>{item.variation})
76 | }
77 | }
78 |
79 | const changeVariation = (index, variation, options) => {
80 | const action = {
81 | id,
82 | variation
83 | }
84 | if (variation) {
85 | wishListDispatch({
86 | type:'UPDATE_VARIATION_WISH',
87 | action
88 | })
89 | updateLocalStorage(wishListState.products)
90 | modalVariation('close')
91 | }
92 | }
93 |
94 | const handleRemove = () => {
95 | if (wishListState) {
96 | if( wishListState.products.length > 0) {
97 | wishListDispatch({
98 | type: 'REMOVE_WISH',
99 | payload: productIndex
100 | })
101 | }
102 | }
103 | }
104 | const handleUnit = (control) => {
105 | if (wishListState) {
106 | if( wishListState.products.length > 0) {
107 | wishListDispatch({
108 | type: 'ADD_UNIT_WISH',
109 | payload: {index: productIndex, control:control}
110 | })
111 | }
112 | }
113 | }
114 | const modalVariation = action => {
115 | if (action == 'open') {
116 | setPopupVariation(true)
117 | } else if (action == 'close') {
118 | setPopupVariation(false)
119 | }
120 | }
121 | const modalOption = action => {
122 | if (action == 'open') {
123 | setPopupOption(true)
124 | } else if (action == 'close') {
125 | setPopupOption(false)
126 | }
127 | }
128 |
129 | return (
130 |
131 | modalVariation('close')}
135 | >
136 |
137 |
138 | {generatePopupVariationContent()}
139 |
140 |
modalVariation('close')}>X
141 |
142 |
143 | modalOption('close')}
147 | >
148 |
149 |
150 | {generatePopupOptionContent()}
151 |
152 |
modalOption('close')}>X
153 |
154 |
155 |
156 |
157 |
158 |

159 |
160 |
161 |
162 |
163 |
164 |
{ name }
165 | { variation &&
Variation:{variation} modalVariation('open')} className="change-variation">
}
166 | { option &&
Option: { option } modalOption('open')} className="change-option">
}
167 |
Qty:
168 |
169 |
handleUnit('+')}>
170 |
{unit}
171 |
handleUnit('-')}>
172 |
173 |
174 |
Price: {price}
175 |
Total: {total}
176 |
177 |
178 |
179 |
180 | )
181 | }
182 |
183 | export default Product
184 |
185 |
186 |
--------------------------------------------------------------------------------
/src/components/WishList/Sass/Index.scss:
--------------------------------------------------------------------------------
1 | @import './../../../styles/variables.scss';
2 |
3 | .WISHLIST-product {
4 | max-width:600px;
5 | margin:40px auto;
6 | }
7 |
8 | .WISHLIST-product-list .card {
9 | margin-bottom:12px;
10 | border-radius: 0;
11 | border:0;
12 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
13 | .card-img {
14 | border-radius: 0;
15 | }
16 | .card-total {
17 | font-weight: 400;
18 | font-family: $primary-font;
19 | font-size:14px;
20 | margin-top: 5px;
21 | color:$secondary-color;
22 | margin-bottom: 5px;;
23 | @media (max-width:767px) {
24 | font-size:14px;
25 | }
26 | }
27 | .card-title {
28 | color:#000;
29 | font-family: $secondary-font;
30 | font-size:13px;
31 | font-weight: 600;
32 | margin:0 0 5px;
33 | text-transform: uppercase;
34 | }
35 | .card-variation,
36 | .card-option,
37 | .card-price,
38 | .card-unit,
39 | .card-total {
40 | margin-bottom: 5px;
41 | font-size:13px;
42 | font-family: $secondary-font;
43 | display: flex;
44 | span {
45 | flex:1;
46 | &:first-child {
47 | max-width: 80px;
48 | text-transform: uppercase;
49 | }
50 | }
51 | }
52 | .card-wrapper {
53 | display: flex;
54 | align-items: center;
55 | position: relative;
56 | }
57 | .left {
58 | max-width:160px;
59 | }
60 | .right {
61 | width:100%;
62 | padding-left: 15px;
63 | .card-units-wrapper {
64 | display: flex;
65 | align-items: center;
66 | ion-icon {
67 | width:13px;
68 | height:13px;
69 | display: flex;
70 | align-items: center;
71 | justify-content: center;
72 | background:$secondary-color;
73 | color:#fff;
74 | }
75 | .card-units {
76 | font-size:13px;
77 | width:16px;
78 | height:16px;
79 | margin:0 3px;
80 | display: flex;
81 | align-items: center;
82 | justify-content: center;
83 | }
84 | }
85 | .card-remove {
86 | position:absolute;
87 | right:8px;
88 | top:8px;
89 | }
90 | }
91 | .change-variation,
92 | .change-option {
93 | color:$secondary-color;
94 | cursor:pointer;
95 | margin-left:6px;
96 | }
97 | }
98 |
99 | .WISHLIST-product-total {
100 | background:#fff;
101 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
102 | margin-bottom: 12px;
103 | margin-top: 20px;
104 | .title {
105 | background:$secondary-color;
106 | color:#fff;
107 | padding: 10px 20px;
108 | font-size:14px;
109 | font-family: $secondary-font;
110 | }
111 | ul {
112 | padding:20px;
113 | li {
114 | display: flex;
115 | margin-bottom: 4px;
116 | span {
117 | flex:1;
118 | text-align: right;
119 | }
120 | &:first-child {
121 | .amount {
122 | font-size:15px;
123 | font-weight: 600;
124 | }
125 | }
126 | &:last-child {
127 | .amount {
128 | font-size:10px;
129 | text-transform: uppercase;
130 | }
131 | }
132 | }
133 | .label {
134 | font-size:13px;
135 | font-weight: $secondary-font;
136 | }
137 | .amount {
138 | font-family: $secondary-font;
139 | }
140 | }
141 | }
142 |
143 | .WISHLIST-shipping-info {
144 | margin-bottom:12px;
145 | border-radius: 0;
146 | border:0;
147 | background:#fff;
148 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
149 | .title {
150 | background:$secondary-color;
151 | color:#fff;
152 | padding: 10px 20px;
153 | font-size:14px;
154 | font-family: $secondary-font;
155 | }
156 | form {
157 | padding:20px;
158 | }
159 | .form-check {
160 | margin-bottom: 20px;
161 | input {
162 | margin-top:3px;
163 | }
164 | label {
165 | font-size:13px;
166 | font-family: $secondary-font;
167 | }
168 | }
169 | .each {
170 | margin-bottom: 8px;
171 | .label {
172 | font-size:13px;
173 | margin-bottom: 8px;
174 | }
175 | .form-group {
176 | margin:0;
177 | }
178 | .form-control {
179 | font-size: 13px;
180 | font-family: $secondary-font;
181 | border:1px solid #eee;
182 | border-radius: 0;
183 | }
184 | }
185 | }
186 |
187 | .WISHLIST-submit {
188 | display: flex;
189 | justify-content: flex-end;
190 | .btn {
191 | font-size:13px;
192 | font-family: $secondary-font;
193 | border-radius: 0;
194 | border-color:$secondary-color;
195 | background:$secondary-color;
196 | }
197 | }
--------------------------------------------------------------------------------
/src/config/firebase.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase';
2 | import "firebase/firestore";
3 | import "firebase/storage"
4 |
5 | // Your web app's Firebase configuration
6 | var firebaseConfig = {
7 | apiKey: "AIzaSyAdIkS1hC2HAdmzxvFZBSC4FY1SKytwMGM",
8 | authDomain: "shop-39810.firebaseapp.com",
9 | databaseURL: "https://shop-39810.firebaseio.com",
10 | projectId: "shop-39810",
11 | storageBucket: "gs://shop-39810.appspot.com/",
12 | messagingSenderId: "218930931613",
13 | appId: "1:218930931613:web:e34792f2e97722b6433174"
14 | };
15 | // Initialize Firebase
16 | firebase.initializeApp(firebaseConfig)
17 |
18 | export const db = firebase.firestore()
19 | export const storage = firebase.storage()
20 | export default firebase;
21 |
22 |
--------------------------------------------------------------------------------
/src/containers/Admin.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react'
2 | import Sidebar from '../layouts/Sidebar'
3 | import Header from '../components/Header/Index'
4 | import ReactNotifications from 'react-notifications-component';
5 | import { BrowserRouter as Router, Route} from 'react-router-dom';
6 | import SignUp from '../components/Auth/SignUp';
7 | import Login from '../components/Auth/Login'
8 | import Products from '../components/Product/Products'
9 | import Category from '../components/Category/Index'
10 | import Segments from '../components/Segments/Index'
11 | import Uploads from '../components/Uploads/Index'
12 | import Hero from '../components/Hero/Index'
13 | import Update from '../components/Update/Index'
14 | import Edit from '../components/Edit/Index'
15 | import WishList from '../components/WishList/Index'
16 | import Order from '../components/Order/Index'
17 | import ViewOrder from '../components/Order/ViewOrder'
18 |
19 | const Admin = () => {
20 | return
21 |
22 |
23 |
24 |
25 |
26 |
27 | {/* */}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {/* */}
37 |
38 |
39 |
40 |
41 |
42 |
43 | }
44 | export default Admin
--------------------------------------------------------------------------------
/src/containers/Filters.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import Filter from '../components/Filter/Filter'
3 |
4 | export default class Filters extends Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/containers/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useContext } from 'react';
2 | import ReactNotifications from 'react-notifications-component';
3 | import 'react-notifications-component/dist/theme.css';
4 | import 'animate.css';
5 | import { Link } from 'react-router-dom'
6 | import firebase, { db } from './../config/firebase';
7 | import './../styles/reset.scss';
8 | import './../styles/global.scss';
9 | import SignUp from '../components/Auth/SignUp';
10 | import Login from '../components/Auth/Login'
11 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
12 | import Products from '../components/Product/Products'
13 | import Category from '../components/Category/Index'
14 | import Segments from '../components/Segments/Index'
15 | import Uploads from '../components/Uploads/Index'
16 | import Header from '../components/Header/Index'
17 | import Hero from '../components/Hero/Index'
18 | import Update from '../components/Update/Index'
19 | import Edit from '../components/Edit/Index'
20 | import WishList from '../components/WishList/Index'
21 | import Order from '../components/Order/Index'
22 | import ViewOrder from '../components/Order/ViewOrder'
23 | import { AuthContext } from '../contexts/AuthContext'
24 | import { WishListContext } from '../contexts/WishListContext'
25 | import { ProductContext } from '../contexts/ProductContext'
26 |
27 | const Home = () => {
28 | const { state, dispatch } = useContext(AuthContext)
29 | const { wishListState, wishListDispatch } = useContext(WishListContext)
30 | const {productState, productDispatch} = useContext(ProductContext)
31 |
32 | useEffect(() => {
33 | authListener()
34 | getProducts()
35 | }, [])
36 |
37 | useEffect(()=> {
38 | console.log('from state :', wishListState)
39 | console.log('from localStorage: ', JSON.parse(localStorage.getItem('wish-list')))
40 | console.log('productState', productState.products)
41 | if(wishListState.products.length <= 0) {
42 | const local = JSON.parse(localStorage.getItem('wish-list'))
43 | if (local) {
44 | if( local.products.length > 0 ) {
45 | wishListDispatch({
46 | type:"REPLACE_WISH",
47 | payload: local.products.map(item => item)
48 | })
49 | }
50 | }
51 | }
52 | })
53 |
54 | const authListener = () => {
55 | firebase.auth().onAuthStateChanged((user) => {
56 | if(user) {
57 | dispatch({
58 | type: "SIGNIN",
59 | payload: user
60 | })
61 | } else {
62 | dispatch({
63 | type: "SIGNIN",
64 | payload: null
65 | })
66 | }
67 | })
68 | }
69 |
70 | const getProducts = () => {
71 | db.collection("products")
72 | .where("status", "==", "available")
73 | .onSnapshot(snapshot => {
74 | const products = []
75 | snapshot.forEach(doc => {
76 | const product = {
77 | id: doc.id,
78 | data: doc.data()
79 | }
80 | products.push(product)
81 | })
82 | productDispatch({
83 | type:'LOAD_PRODUCTS',
84 | payload: [...products]
85 | })
86 | });
87 | }
88 | return (
89 |
90 |
91 |
92 |
93 | TEMPORARY LINKS
94 |
95 | - PRODUCTS
96 | - ORDERS
97 | - ADD PRODUCTS
98 | - UPDATE PRODUCTS
99 | - WISHLIST
100 | - PRODUCT CATEGORY
101 | - PRODUCT SEGMENT
102 | - SIGN UP
103 | - LOGIN
104 |
105 |
106 |
107 | { }
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 | );
127 | }
128 | export default Home;
129 |
--------------------------------------------------------------------------------
/src/containers/Shop.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import Products from '../components/Product/Products';
3 | import Filters from './Filters';
4 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
5 |
6 | function Shop() {
7 | return (
8 |
9 | {/*
10 | */}
11 |
12 |
13 | );
14 | }
15 |
16 | export default Shop;
17 |
--------------------------------------------------------------------------------
/src/contexts/AuthContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react'
2 | import AuthReducer from '../reducers/AuthReducer'
3 |
4 | export const AuthContext = createContext()
5 |
6 | const initState = {
7 | user: {}
8 | }
9 | const AuthProvider = props => {
10 | const [ state, dispatch ] = useReducer(AuthReducer, initState)
11 | const value = {state, dispatch}
12 | return (
13 |
14 | {props.children}
15 |
16 | )
17 | }
18 |
19 | export default AuthProvider
--------------------------------------------------------------------------------
/src/contexts/ProductContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react'
2 | import ProductReducer from '../reducers/ProductReducer'
3 |
4 | export const ProductContext = createContext()
5 |
6 | const initState = {
7 | products: []
8 | }
9 |
10 | const ProductProvider = props => {
11 | const [ productState, productDispatch ] = useReducer(ProductReducer, initState)
12 | const value = {productState, productDispatch}
13 | return (
14 |
15 | {props.children}
16 |
17 | )
18 | }
19 |
20 | export default ProductProvider
--------------------------------------------------------------------------------
/src/contexts/WishListContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react'
2 | import WishListReducer from '../reducers/WishListReducer'
3 | export const WishListContext = createContext()
4 |
5 | const initState = {
6 | products: [],
7 | select_variation:null
8 | }
9 | const WishListProvider = props => {
10 | const [ wishListState, wishListDispatch ] = useReducer(WishListReducer, initState)
11 | const value = {wishListState, wishListDispatch }
12 | return (
13 |
14 | {props.children}
15 |
16 | )
17 | }
18 |
19 | export default WishListProvider
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Home from './containers/Home';
4 | import Admin from './containers/Admin'
5 | import AuthProvider from './contexts/AuthContext'
6 | import WishListProvider from './contexts/WishListContext';
7 | import ProductProvider from './contexts/ProductContext';
8 | import { Provider } from 'react-redux'
9 | import store from './redux/store'
10 |
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | , document.getElementById('root'));
23 |
24 |
--------------------------------------------------------------------------------
/src/layouts/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useState } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { withRouter } from 'react-router-dom'
4 | import firebase from './../config/firebase'
5 | import { AuthContext } from './../contexts/AuthContext'
6 | import './../styles/variables.scss';
7 |
8 | const selected = {
9 | backgroundColor:'#00807d'
10 | }
11 |
12 | const Sidebar = () => {
13 | const [current, setCurrent] = useState(1)
14 | return
15 |
16 |
17 | {(value) => {
18 | if ( value.state.user === null || Object.keys(value.state.user).length === 0 ) {
19 | return
23 | } else {
24 | return (
25 |
26 |
27 |
{value.state.user.user.displayName.slice(0,1)}
28 |
{value.state.user.user.displayName}
29 |
30 |
31 | - setCurrent(1)} to="/">Products
32 | - setCurrent(2)} to="/order"> List Of Orders
33 | - setCurrent(3)} to="/update"> Update Products
34 | - setCurrent(4)} to="/upload"> Add Product
35 | - setCurrent(5)} to="/category"> Categories
36 | - setCurrent(6)} to="/segments"> Segments
37 |
38 | )
39 | }
40 | }}
41 |
42 |
43 |
44 |
45 | }
46 |
47 | export default withRouter(Sidebar)
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reducers/AuthReducer.js:
--------------------------------------------------------------------------------
1 | const AuthReducer = (state, action) => {
2 | switch (action.type) {
3 | case "LOGIN":
4 | return {...state, user: action.payload}
5 | case "SIGNIN":
6 | return {...state, user: action.payload}
7 | case "LOGOUT":
8 | return {...state, user: action.payload}
9 | default:
10 | return state
11 | }
12 | }
13 |
14 | export default AuthReducer;
--------------------------------------------------------------------------------
/src/reducers/ProductReducer.js:
--------------------------------------------------------------------------------
1 | const ProductReducer = (state, action) => {
2 | switch (action.type) {
3 | case "LOAD_PRODUCTS":
4 | return {...state, products: [...action.payload]}
5 | case "ADD_PRODUCT":
6 | return {...state, products: [...state.products, action.payload]}
7 | default:
8 | return state
9 | }
10 | }
11 | export default ProductReducer;
--------------------------------------------------------------------------------
/src/reducers/WishListReducer.js:
--------------------------------------------------------------------------------
1 | const WishListReducer = (state, action) => {
2 | switch (action.type) {
3 | case "ADD_WISH":
4 | return addWish(state, action)
5 | case "REPLACE_WISH":
6 | return replaceWish(state, action)
7 | case "REMOVE_WISH":
8 | return removeWish(state, action)
9 | case "ADD_UNIT_WISH":
10 | return unitWish(state, action)
11 | case "UPDATE_VARIATION_WISH":
12 | return updateVariation(state, action)
13 | case "UPDATE_OPTION_WISH":
14 | return updateOption(state, action)
15 | default:
16 | return state
17 | }
18 | }
19 |
20 | const addWish = (state, action) => {
21 | localStorage.setItem('wish-list', JSON.stringify( {...state, products: [...state.products, action.payload]}))
22 | return {...state, products: [...state.products, action.payload]}
23 | }
24 |
25 | const replaceWish = ( state, action ) => {
26 | localStorage.setItem('wish-list', JSON.stringify( {...state, products: [...action.payload]} ))
27 | return {...state, products: [...action.payload]}
28 | }
29 |
30 | const removeWish = ( state, action ) => {
31 | const copyState = {...state}
32 | copyState.products.splice(action.payload, 1);
33 | localStorage.setItem('wish-list', JSON.stringify( {...state, products: [...copyState.products]} ))
34 | return {...state, products: [...copyState.products]}
35 | }
36 |
37 | const unitWish = ( state, action ) => {
38 | const copyState = { ...state }
39 | const index = copyState.products[action.payload.index]
40 | const ctrl = action.payload.control
41 | const unit = index.unit
42 | const price = index.price
43 | if (ctrl == "+") {
44 | index.unit = Number(unit) + 1
45 | index.total = Number(price) * ( Number(unit) + 1 )
46 | }
47 | if (ctrl == "-") {
48 | if (Number(unit) <= 1) {
49 | index.unit = 1
50 | } else {
51 | index.unit = Number(unit) - 1
52 | index.total = Number(price) * ( Number(unit) - 1 )
53 | }
54 | }
55 | localStorage.setItem('wish-list', JSON.stringify( {...state, products: [...copyState.products]} ))
56 | return {...state, products: [...copyState.products]}
57 | }
58 |
59 | const updateVariation = ( state, action) => {
60 | const id = action.action.id
61 | const variation = action.action.variation
62 | const copyState = {...state}
63 | const product = copyState.products.find(item => item.id == id)
64 | product.variation = variation
65 | return {...state, products: [...copyState.products]}
66 | }
67 |
68 | const updateOption = ( state, action) => {
69 | const id = action.action.id
70 | const option = action.action.option
71 | const price = action.action.price
72 | const copyState = {...state}
73 | const product = copyState.products.find(item => item.id == id)
74 | product.option = option
75 | product.price = price
76 | product.total = product.unit * price
77 | return {...state, products: [...copyState.products]}
78 | }
79 |
80 |
81 | export default WishListReducer;
--------------------------------------------------------------------------------
/src/redux/actions/order/orderActions.js:
--------------------------------------------------------------------------------
1 | import { db } from './../../../config/firebase'
2 |
3 | import {
4 | ORDER_LOADING,
5 | ORDER_ERROR,
6 | ORDER_SUCCESS,
7 | ORDER_RESET,
8 | GET_ORDERS,
9 | SELECT_ORDER,
10 | DELETE_ORDER,
11 | PROCEED_TO_ON_SHIP,
12 | RECEIVE_ORDER,
13 | GET_ORDERS_BY_CUSTOMER,
14 | ADD_QUANTITY,
15 | SUBTRACT_QUANTITY,
16 | REMOVE_TO_ORDER,
17 | EMPTY_ORDER,
18 | SUM_TOTAL_IN_THE_ORDER,
19 | UPDATE_SHIPPING_FEE,
20 | PROCESS_ORDER
21 | } from './orderTypes'
22 |
23 | export const getOrders = (orderStatus) => {
24 |
25 | return (dispatch) => {
26 | dispatch({
27 | type: ORDER_LOADING
28 | })
29 | dispatch({
30 | type: ORDER_RESET
31 | })
32 | db.collection("orders")
33 | .where("status", "==", orderStatus)
34 | .get()
35 | .then(snapshot => {
36 | const orders = []
37 | snapshot.forEach(doc => {
38 | const obj = {
39 | id: doc.id,
40 | name: doc.data()
41 | }
42 | orders.push(obj)
43 | })
44 | const sortedOrders = orders.sort((a,b) => a.name.timestamp - b.name.timestamp)
45 | dispatch({
46 | type: GET_ORDERS,
47 | payload: sortedOrders
48 | })
49 | }, function (error) {
50 | dispatch({
51 | type: ORDER_ERROR,
52 | payload: error
53 | })
54 | });
55 | }
56 | }
57 | export const getOrdersByCustomer = (orderStatus, customerId) => {
58 |
59 | return (dispatch) => {
60 | dispatch({
61 | type: ORDER_LOADING
62 | })
63 | dispatch({
64 | type: ORDER_RESET
65 | })
66 | db.collection("orders")
67 | .where("status", "==", orderStatus)
68 | .where("uid", "==", customerId)
69 | .get()
70 | .then(snapshot => {
71 | const orders = []
72 | snapshot.forEach(doc => {
73 | const obj = {
74 | id: doc.id,
75 | name: doc.data()
76 | }
77 | orders.push(obj)
78 | })
79 | const sortedOrders = orders.sort((a,b) => a.name.timestamp - b.name.timestamp)
80 | dispatch({
81 | type: GET_ORDERS_BY_CUSTOMER,
82 | payload: sortedOrders
83 | })
84 | }, function (error) {
85 | dispatch({
86 | type: ORDER_ERROR,
87 | payload: error
88 | })
89 | });
90 | }
91 | }
92 | export const orderProducts = (orderObj) => {
93 | return (dispatch) => {
94 | dispatch({
95 | type: ORDER_LOADING
96 | })
97 | db.collection("orders").add(orderObj)
98 | .then(function(docRef) {
99 | dispatch({
100 | type: ORDER_SUCCESS
101 | })
102 | })
103 | .catch(function(error) {
104 | dispatch({
105 | type: ORDER_ERROR,
106 | payload: error
107 | })
108 | });
109 | }
110 | }
111 | export const orderReset = () => {
112 | return {
113 | type: ORDER_RESET
114 | }
115 | }
116 | export const selectOrder = (orderId) => {
117 | return {
118 | type: SELECT_ORDER,
119 | payload: orderId
120 | }
121 | }
122 | export const deleteOrder = (orderId) => {
123 | return (dispatch) => {
124 | db.collection("orders")
125 | .doc(orderId)
126 | .delete()
127 | .then(function() {
128 | dispatch({
129 | type: DELETE_ORDER
130 | })
131 | }).catch(function(error) {
132 | console.error("Error removing document: ", error);
133 | });
134 | }
135 | }
136 | export const proceedToOnShip = (orderId) => {
137 | return (dispatch) => {
138 | db.collection("orders").doc(orderId).update({
139 | status: "On Ship"
140 | }).then(function() {
141 | dispatch({
142 | type: PROCEED_TO_ON_SHIP
143 | })
144 | }).catch(function(error) {
145 | console.error("Error on proceeding order: ", error);
146 | });
147 | }
148 | }
149 | export const receiveOrder = (orderId) => {
150 | return (dispatch) => {
151 | db.collection("orders").doc(orderId).update({
152 | status: "Received"
153 | }).then(function() {
154 | dispatch({
155 | type: RECEIVE_ORDER
156 | })
157 | }).catch(function(error) {
158 | console.error("Error on proceeding order: ", error);
159 | });
160 | }
161 | }
162 | export const removeToOrder = (index) => {
163 | return {
164 | type: REMOVE_TO_ORDER,
165 | payload: index
166 | }
167 | }
168 | export const addQuantity = (index) => {
169 | return {
170 | type: ADD_QUANTITY,
171 | payload: index
172 | }
173 | }
174 | export const subtractQuantity = (index) => {
175 | return {
176 | type: SUBTRACT_QUANTITY,
177 | payload: index
178 | }
179 | }
180 | export const sumProductsInOrder = () => {
181 | return {
182 | type: SUM_TOTAL_IN_THE_ORDER
183 | }
184 | }
185 | export const emptyOrder = () => {
186 | return {
187 | type: EMPTY_ORDER
188 | }
189 | }
190 | export const updateShippingFee = (fee) => {
191 | return {
192 | type: UPDATE_SHIPPING_FEE,
193 | payload:fee
194 | }
195 | }
196 | export const proccessOrder = (order, orderId) => {
197 | const newObj = {
198 | notes: order.name.notes,
199 | order_date:order.name.order_date,
200 | shipping_fee:order.name.shipping_fee,
201 | products:order.name.products,
202 | shipping_details: order.name.shipping_details,
203 | status:'Reviewed',
204 | timestamp:order.name.timestamp,
205 | total_amount:order.name.total_amount,
206 | transaction_id:order.name.transaction_id,
207 | uid:order.name.uid
208 | }
209 | return (dispatch) => {
210 | db.collection("orders").doc(orderId)
211 | .set(newObj)
212 | .then(function(docRef) {
213 | dispatch({
214 | type: 'PROCESS_ORDER'
215 | })
216 | })
217 | .catch(function(error) {
218 | console.log("error", error.message)
219 | });
220 | }
221 |
222 | }
223 |
224 |
--------------------------------------------------------------------------------
/src/redux/actions/order/orderTypes.js:
--------------------------------------------------------------------------------
1 | export const ORDER_PRODUCTS = 'ORDER_PRODUCTS'
2 | export const ORDER_LOADING = 'ORDER_LOADING'
3 | export const ORDER_SUCCESS = 'ORDER_SUCCESS'
4 | export const ORDER_ERROR = 'ORDER_ERROR'
5 | export const ORDER_RESET = 'ORDER_RESET'
6 | export const GET_ORDERS = 'GET_ORDERS'
7 | export const SELECT_ORDER = 'SELECT_ORDER'
8 | export const DELETE_ORDER = 'DELETE_ORDER'
9 | export const PROCEED_TO_ON_SHIP = 'PROCEED_TO_ON_SHIP'
10 | export const RECEIVE_ORDER = 'RECEIVE_ORDER'
11 | export const GET_ORDERS_BY_CUSTOMER = 'GET_ORDERS_BY_CUSTOMER'
12 | export const ADD_QUANTITY = 'ADD_QUANTITY'
13 | export const SUBTRACT_QUANTITY = 'SUBTRACT_QUANTITY'
14 | export const REMOVE_TO_ORDER = 'REMOVE_TO_ORDER'
15 | export const EMPTY_ORDER = 'EMPTY_ORDER'
16 | export const UPDATE_SHIPPING_FEE = 'UPDATE_SHIPPING_FEE'
17 | export const SUM_TOTAL_IN_THE_ORDER = 'SUM_TOTAL_IN_THE_ORDER'
18 | export const PROCESS_ORDER = 'PROCESS_ORDER'
--------------------------------------------------------------------------------
/src/redux/reducers/orderReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | ORDER_SUCCESS,
3 | ORDER_LOADING,
4 | ORDER_ERROR,
5 | ORDER_RESET,
6 | GET_ORDERS,
7 | SELECT_ORDER,
8 | DELETE_ORDER,
9 | GET_ORDERS_BY_CUSTOMER,
10 | ADD_QUANTITY,
11 | SUBTRACT_QUANTITY,
12 | REMOVE_TO_ORDER,
13 | EMPTY_ORDER,
14 | SUM_TOTAL_IN_THE_ORDER,
15 | UPDATE_SHIPPING_FEE,
16 | PROCESS_ORDER
17 | } from './../actions/order/orderTypes'
18 |
19 | const initialState = {
20 | orders:[],
21 | isSuccess:false,
22 | ordersLoading:false,
23 | message:'',
24 | selectedOrderId:'',
25 | selectedOrder:{}
26 | }
27 |
28 | const reducer = ( state = initialState, action) => {
29 | switch (action.type) {
30 | case GET_ORDERS :
31 | return {
32 | ...state,
33 | ordersLoading: false,
34 | orders:action.payload
35 | }
36 | case GET_ORDERS_BY_CUSTOMER :
37 | return {
38 | ...state,
39 | ordersLoading: false,
40 | orders:action.payload
41 | }
42 | case SELECT_ORDER:
43 | return {
44 | ...state,
45 | selectedOrderId : action.payload,
46 | selectedOrder: selectedOrderFunc(state, action.payload)
47 | }
48 | case DELETE_ORDER:
49 | return {
50 | ...state
51 | }
52 | case ORDER_SUCCESS :
53 | return {
54 | ...state,
55 | isSuccess:true,
56 | ordersLoading:false,
57 | message:''
58 | }
59 | case ORDER_RESET:
60 | return {
61 | ...state,
62 | isSuccess:false,
63 | ordersLoading:false,
64 | message:''
65 | }
66 | case ORDER_LOADING :
67 | return {
68 | ...state,
69 | ordersLoading: true,
70 | message:'',
71 | orders:[]
72 | }
73 | case ORDER_ERROR :
74 | return {
75 | ...state,
76 | ordersLoading: false,
77 | isSuccess:false,
78 | message: action.payload
79 | }
80 | case REMOVE_TO_ORDER :
81 | return {
82 | ...state,
83 | orders: removeToOrder(state, action.payload)
84 | }
85 | case ADD_QUANTITY :
86 | return {
87 | ...state,
88 | selectedOrder: addQTY(state, action.payload)
89 | }
90 | case SUBTRACT_QUANTITY :
91 | return {
92 | ...state,
93 | selectedOrder: subtractQTY(state, action.payload)
94 | }
95 | case SUM_TOTAL_IN_THE_ORDER :
96 | return {
97 | ...state,
98 | selectedOrder: {...state.selectedOrder, ...state.selectedOrder.name.total_amount = sumProductsInOrder(state)}
99 | }
100 | case EMPTY_ORDER :
101 | return {
102 | ...state,
103 | orders: [],
104 | }
105 | case UPDATE_SHIPPING_FEE :
106 | return {
107 | ...state,
108 | selectedOrder:{...state.selectedOrder, ...state.selectedOrder.name.shipping_fee = action.payload}
109 | }
110 | case PROCESS_ORDER :
111 | return {
112 | ...state,
113 | }
114 | default : return state
115 | }
116 | }
117 |
118 | export default reducer
119 |
120 | const selectedOrderFunc = (state, id ) => {
121 | const orders = [...state.orders]
122 | if(id) {
123 | const index = orders.findIndex(e => e.id === id)
124 | if(index !== null || index !== undefined) {
125 | return orders[index]
126 | }
127 | }
128 | return {}
129 | }
130 |
131 | const addQTY = (state, index) => {
132 | const selectedOrder = {...state.selectedOrder}
133 | const orders = selectedOrder.name.products
134 | if (index !== undefined || index !== null || index !== '') {
135 | if(orders.length) {
136 | const productObj = {
137 | id: orders[index].id,
138 | name: orders[index].name,
139 | option:orders[index].option,
140 | price:orders[index].price,
141 | qty:orders[index].qty + 1,
142 | variation:orders[index].variation,
143 | cover:orders[index].cover,
144 | total: ( orders[index].qty + 1 ) * Number(orders[index].price)
145 | }
146 | orders[index] = productObj
147 | return selectedOrder
148 | } else {
149 | return state.selectedOrder
150 | }
151 | } else {
152 | return state.selectedOrder
153 | }
154 | }
155 | const subtractQTY = (state, index) => {
156 | const selectedOrder = {...state.selectedOrder}
157 | const orders = selectedOrder.name.products
158 | if (index !== undefined || index !== null || index !== '') {
159 | if(orders.length) {
160 | const productObj = {
161 | id: orders[index].id,
162 | name: orders[index].name,
163 | option:orders[index].option,
164 | price:orders[index].price,
165 | qty:orders[index].qty <= 1 ? 1 : orders[index].qty - 1,
166 | variation:orders[index].variation,
167 | cover:orders[index].cover,
168 | total: ( orders[index].qty <= 1 ? 1 : orders[index].qty - 1 ) * Number(orders[index].price)
169 | }
170 | orders[index] = productObj
171 | return selectedOrder
172 | } else {
173 | return state.selectedOrder
174 | }
175 | } else {
176 | return state.selectedOrder
177 | }
178 | }
179 | const removeToOrder = (state, index) => {
180 | const selectedOrder = {...state.selectedOrder}
181 | const orders = selectedOrder.name.products
182 | if (index !== undefined || index !== null || index !== '') {
183 | if(orders.length) {
184 | orders.splice(index, 1)
185 | return orders
186 | } else {
187 | return state.selectedOrder
188 | }
189 | } else {
190 | return state.selectedOrder
191 | }
192 | }
193 | const sumProductsInOrder = (state) => {
194 | const selectedOrder = {...state.selectedOrder}
195 | const orders = selectedOrder.name.products
196 | const shipping_fee = selectedOrder.name.shipping_fee
197 | if(orders.length) {
198 | const total = orders.map(item => item.total)
199 | .reduce((prev, curr) => prev + curr, 0);
200 | return total + Number(shipping_fee)
201 | } else {
202 | return 0
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/redux/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import orderReducer from './orderReducer'
3 | const rootReducer = combineReducers({
4 | orders: orderReducer,
5 | })
6 | export default rootReducer
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore} from 'redux'
2 | import thunkMiddleware from 'redux-thunk'
3 | import { composeWithDevTools } from 'redux-devtools-extension';
4 | import rootReducer from './reducers/rootReducer'
5 |
6 | const store = createStore(rootReducer, composeWithDevTools(
7 | applyMiddleware(thunkMiddleware),
8 | // other store enhancers if any
9 | ));
10 |
11 | export default store
--------------------------------------------------------------------------------
/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/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 |
3 | $primary-color: rgb(247,249,252);
4 | $secondary-color:#00807d;
5 | $primary-font: 'Roboto', sans-serif;
6 |
7 | body {
8 | padding:0;
9 | margin:0;
10 | font-family: $primary-font;
11 | font-weight: 300;
12 | width:100%;
13 | overflow-x:hidden;
14 | overflow-y: scroll;
15 | background:$primary-color;
16 | }
17 |
18 | .container {
19 | max-width: 100%;
20 | }
21 |
22 | .quicklinks {
23 | text-align: center;
24 | margin-bottom:30px;
25 | ul {
26 | display: flex;
27 | flex-wrap: wrap;
28 | padding-top:10px;
29 | justify-content: center;
30 | }
31 | li {
32 | padding:5px 10px;
33 | border:1px solid #efefef;
34 | margin:4px 5px;
35 | }
36 | }
37 |
38 | .AppWrapper {
39 | display: flex;
40 | height: auto;
41 | min-height: 100vh;
42 | position: relative;
43 | padding-top:64px;
44 | padding-left:264px;
45 | }
46 |
47 | .MainSidebar {
48 | min-width: 264px;
49 | background: #3ab3a8;
50 | background: linear-gradient(to bottom, #3ab3a8 0, #37a0ac 100%);
51 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3ab3a8', endColorstr='#37a0ac', GradientType=0);
52 | position: fixed;
53 | top:0;
54 | left:0;
55 | height:100vh;
56 | padding-top:64px;
57 | ul {
58 | width: 100%;
59 | padding:30px;
60 | padding-top:20px;
61 | ion-icon {
62 | margin-right:8px;
63 | }
64 | a{
65 | color:#fff;
66 | text-transform: capitalize !important;
67 | padding: 12px;
68 | text-decoration: none;
69 | border-radius: 2px;
70 | transition: 0.3s;
71 | position: relative;
72 | outline: none;
73 | display: flex;
74 | align-items: center;
75 |
76 | &:hover {
77 | background:$secondary-color;
78 | }
79 | &:after {
80 | display: inline-block;
81 | width: 0;
82 | height: 0;
83 | position: absolute;
84 | right: 10px;
85 | top:18px;
86 | transform:rotate(-90deg);
87 | vertical-align: 0.255em;
88 | content: "";
89 | border-top: 0.3em solid;
90 | border-right: 0.3em solid transparent;
91 | border-bottom: 0;
92 | border-left: 0.3em solid transparent;
93 | }
94 | }
95 | }
96 | .user-profile {
97 | margin:0;
98 | padding:30px;
99 | display: flex;
100 | flex-direction: column;
101 | align-items: center;
102 | background: #3ab3a8;
103 | background: linear-gradient(to top, #3ab3a8 0, #37a0ac 100%);
104 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3ab3a8', endColorstr='#37a0ac', GradientType=0);
105 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0,0,0,0.02);
106 |
107 | .user-initial {
108 | width:60px;
109 | height:60px;
110 | background:#495057;
111 | display: flex;
112 | align-items: center;
113 | justify-content: center;
114 | color:#fff;
115 | border-radius: 50%;
116 | margin-bottom: 12px;
117 | font-size: 26px;
118 | }
119 | .user-name {
120 | color:#fff;
121 | font-size: 18px;
122 | font-family: $primary-font;
123 | font-weight: 300;
124 | }
125 | }
126 | }
127 |
128 | .MainContent {
129 | width: 100%;
130 | }
131 |
132 | //orders
133 | .order-search-id {
134 | padding:30px;
135 | background:#f2f4f7;
136 | span {
137 | font-family: "PT Sans", sans-serif;
138 | letter-spacing: 0.5px;
139 | text-transform: uppercase;
140 | margin-bottom: 6px;
141 | display: block;
142 | }
143 | .search-input {
144 | display: flex;
145 | }
146 |
147 | }
148 | .order-radio {
149 | margin-bottom: 10px;
150 | label {
151 | input {
152 | margin-right: 5px;
153 | }
154 | }
155 | }
156 | .list-of-orders {
157 | display: flex;
158 | justify-content: space-between;
159 | flex-wrap: wrap;
160 | .card {
161 | margin-bottom: 12px;
162 | border-radius: 6px !important;
163 | overflow: hidden;
164 | border: 0;
165 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0, 0, 0, 0.02);
166 | max-width: 32%;
167 | width:100%;
168 | display: flex;
169 | justify-content: space-between;
170 | flex-direction: row;
171 | padding:25px 16px;
172 | cursor:pointer;
173 | transition: 0.3s;
174 | &:hover {
175 | box-shadow: 0 0.25rem 0.125rem 0 rgba(0, 0, 0, 0.06);
176 | span {
177 | color:'#00807d' !important;
178 | }
179 | }
180 | @media (max-width:1199px) {
181 | max-width: 48.5%;
182 | }
183 | span {
184 | font-weight: 600;
185 | font-size: 14px;
186 | transition: 0.3s;
187 | position: relative;
188 | top:2px;
189 | }
190 | }
191 | }
192 |
193 | // RESET STRAP
194 |
195 | .list-group-item {
196 | padding: 1.1rem 1.25rem;
197 | border: 0.5px solid #e8e8e8;
198 | display: flex;
199 | align-items: center;
200 | justify-content: space-between;
201 | span {
202 | display: flex;
203 | align-items: center;
204 | }
205 | span.badge {
206 | background:$secondary-color;
207 | border-radius: 50%;
208 | color:#fff;
209 | width:20px;
210 | height:20px;
211 | display: inline-flex;
212 | align-items: center;
213 | justify-content: center;
214 | margin-right:10px;
215 | font-size:11px;
216 | }
217 | &:hover{
218 | background-color: #f6f7fb;
219 | }
220 | }
221 |
222 | //Card css
223 | .card{
224 | margin-bottom: $card-margin-bottom;
225 | border: $card-border-width;
226 | transition: all 0.3s ease;
227 | letter-spacing: 0.5px;
228 | border-radius: $card-border-radious;
229 | box-shadow: $card-box-shadow;
230 | .card-header{
231 | background-color: $card-header-bg-color;
232 | border-bottom: none;
233 | padding: $card-padding;
234 | border-bottom: 1px solid $card-border-color;
235 | border-top-left-radius: $card-border-radious;
236 | border-top-right-radius: $card-border-radious;
237 | h5{
238 | font-size:$card-header-font-size;
239 | margin-bottom: 0;
240 | text-transform: $card-header-font-transform;
241 | font-weight: 400;
242 | font-family: "Work sans", sans-serif;
243 | }
244 | > span{
245 | font-size: $card-header-span-size;
246 | color: $card-header-span-color;
247 | margin-top: 5px;
248 | display: block;
249 | letter-spacing: 1px;
250 | }
251 | }
252 | .card-body{
253 | padding: $card-padding;
254 | background-color: $card-body-bg-color;
255 | p{
256 | &:last-child{
257 | margin-bottom: 0;
258 | }
259 | }
260 | }
261 | .sub-title{
262 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
263 | padding-bottom: 5px;
264 | margin-bottom: 8px;
265 | font-size: 18px;
266 | }
267 | .card-footer{
268 | background-color: $card-footer-bg-color;
269 | border-top: 1px solid $card-border-color;
270 | padding: $card-padding;
271 | border-bottom-left-radius: $card-border-radious;
272 | border-bottom-right-radius: $card-border-radious;
273 | }
274 | }
275 |
276 | // Alerts
277 |
278 | div.fade.alert {
279 | z-index: 99999999 !important;
280 | }
281 |
--------------------------------------------------------------------------------
/src/styles/mixins.scss:
--------------------------------------------------------------------------------
1 | // ROYA MIXINS
2 | //cross browser Mixin
3 | @mixin placeHolder() {
4 | &::-webkit-input-placeholder {
5 | //* Chrome/Opera/Safari */
6 | @content;
7 | }
8 |
9 | &::-moz-placeholder {
10 | //* Firefox 19+ */
11 | @content;
12 | }
13 |
14 | &:-ms-input-placeholder {
15 | //* IE 10+ */
16 | @content;
17 | }
18 |
19 | &:-moz-placeholder {
20 | //* Firefox 18- */
21 | @content;
22 | }
23 |
24 | &::-ms-input-placeholder {
25 | @content;
26 | }
27 | }
28 | @mixin ios-device(){
29 | @supports (-webkit-overflow-scrolling: touch) {
30 | @content;
31 | //CSS specific to iOS devices
32 | }
33 | }
34 | @mixin not-ios-device(){
35 | @supports not (-webkit-overflow-scrolling: touch) {
36 | @content;
37 | // CSS for other than iOS devices
38 | }
39 | }
40 | @mixin ie(){
41 | @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
42 | @content;
43 | //CSS for IE explorer
44 | }
45 | }
46 | @mixin firefox(){
47 | @media all and (min--moz-device-pixel-ratio:0) and (max-width: 680px){
48 | @content;
49 | }
50 | }
51 | @mixin edge(){
52 | @supports (-ms-ime-align: auto) {
53 | @content;
54 | }
55 | }
56 |
57 | //Responsive Mixins
58 | @mixin aspect-ratio($width, $height) {
59 | position: relative;
60 | &:before {
61 | display: block;
62 | content: "";
63 | width: 100%;
64 | padding-top: ($height / $width) * 100%;
65 | }
66 | > img {
67 | position: absolute;
68 | top: 0;
69 | left: 0;
70 | right: 0;
71 | bottom: 0;
72 | }
73 | }
74 |
75 | // Media query >
76 | @mixin min($media){
77 | @media(min-width:$media) { @content; }
78 | }
79 |
80 | // Media query <
81 | @mixin max($media){
82 | @media(max-width:$media) {
83 | @content;
84 | }
85 | }
86 |
87 | // Media query <>
88 | @mixin between($min, $max) {
89 | @media screen and (min-width: $min) and (max-width: $max) {
90 | @content;
91 | }
92 | }
93 |
94 | @mixin respond-vh($min,$max,$wmin,$wmax){
95 | @media only all and (min-height:$min) and (max-height:$max) and (min-width:$wmin) and (max-width:$wmax) {
96 | @content;
97 | }
98 | }
99 | // Set container size
100 | @function calc-vw-($column){
101 | @return $column/12 * 100vw;
102 | }
103 | @mixin ry-container($col,$max-w,$mob-col){
104 | width: 100% !important;
105 | @include between(992px,1920px){
106 | width: calc(100% - calc-col($col));
107 | padding-left: calc-col($col) !important;
108 | padding-right: calc-col($col) !important;
109 | }
110 | @include min(1921px){
111 | max-width: $max-w !important;
112 | }
113 | @include max(991px){
114 | padding-right: calc-col($mob-col) !important;
115 | padding-left: calc-col($mob-col) !important;
116 | }
117 | }
118 |
119 | @mixin ry-contain($col,$max-w,$mob-col){
120 | width: 100%;
121 | @include between(992px,1920px){
122 | width: calc(100% - calc-col($col));
123 | padding-left: calc-col($col);
124 | padding-right: calc-col($col);
125 | }
126 | @include min(1921px){
127 | max-width: $max-w;
128 | }
129 | @include max(991px){
130 | padding-right: calc-col($mob-col);
131 | padding-left: calc-col($mob-col);
132 | }
133 | }
134 | @function fluidWdth($width, $cWidth) {
135 | @return $width / $cWidth * 100%;
136 | }
137 | @mixin contain($wdt) {
138 | max-width: $wdt;
139 | margin: 0 auto;
140 | float: none;
141 | }
142 | @mixin section-contain($w,$breakPoint, $percent) {
143 | @include contain(fluidWdth($w,1920px));
144 | @include min(992px) {
145 | max-width: $w;
146 | }
147 | @include between(992px,$breakPoint) {
148 | max-width: $percent;
149 | }
150 | @include max(991px) {
151 | max-width: $percent;
152 | }
153 | }
154 |
155 | //font responsive
156 | @mixin font-size($min-font-size: 16px, $max-font-size: 21px, $lower-range: 420px, $upper-range: 991px) {
157 | font-size: calc(#{$min-font-size} + #{(($max-font-size / ($max-font-size * 0 + 1)) - ($min-font-size / ($min-font-size * 0 + 1)))} * ((100vw - #{$lower-range}) / #{(($upper-range / ($upper-range * 0 + 1)) - ($lower-range / ($lower-range * 0 + 1)))}));
158 | @media screen and (max-width: $lower-range) {
159 | font-size: $min-font-size;
160 | }
161 | @media screen and (min-width: $upper-range) {
162 | font-size: $max-font-size;
163 | }
164 | }
165 |
166 | // ********** Transition ***********//
167 | //Possible timing values
168 | $primary-timing:cubic-bezier(0.165, 0.84, 0.44, 1);
169 | $secondary-timing:cubic-bezier(0.215,.61,.355,1);
170 |
171 | //How to use: @include primary-transition(ease, all, 400ms, 0s)
172 | @mixin primary-transition($timing: $primary-timing, $target:all, $duration:0.3s, $delay:0s) {
173 | transition: #{$target} #{$duration} #{$timing} #{$delay};
174 | }
175 |
176 | //How to use: @include primary-loop-transition(7, ease, 400ms)
177 | @mixin primary-loop-transition($length, $timing: $primary-timing, $duration:100ms) {
178 | $delay: 0;
179 | @for $i from 1 through $length {
180 | &:nth-child(#{$i}) {
181 | $delay: $delay + $duration;
182 | @include primary-transition($timing, all, $duration, $delay);
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/styles/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0-modified | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 |
27 | /* make sure to set some focus styles for accessibility */
28 | :focus {
29 | outline: 0;
30 | }
31 |
32 | /* HTML5 display-role reset for older browsers */
33 | article, aside, details, figcaption, figure,
34 | footer, header, hgroup, menu, nav, section {
35 | display: block;
36 | }
37 |
38 | body {
39 | line-height: 1;
40 | }
41 |
42 | ol, ul {
43 | list-style: none;
44 | }
45 |
46 | blockquote, q {
47 | quotes: none;
48 | }
49 |
50 | blockquote:before, blockquote:after,
51 | q:before, q:after {
52 | content: '';
53 | content: none;
54 | }
55 |
56 | table {
57 | border-collapse: collapse;
58 | border-spacing: 0;
59 | }
60 |
61 | input[type=search]::-webkit-search-cancel-button,
62 | input[type=search]::-webkit-search-decoration,
63 | input[type=search]::-webkit-search-results-button,
64 | input[type=search]::-webkit-search-results-decoration {
65 | -webkit-appearance: none;
66 | -moz-appearance: none;
67 | }
68 |
69 | input[type=search] {
70 | -webkit-appearance: none;
71 | -moz-appearance: none;
72 | -webkit-box-sizing: content-box;
73 | -moz-box-sizing: content-box;
74 | box-sizing: content-box;
75 | }
76 |
77 | textarea {
78 | overflow: auto;
79 | vertical-align: top;
80 | resize: vertical;
81 | }
82 |
83 | /**
84 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
85 | */
86 |
87 | audio,
88 | canvas,
89 | video {
90 | display: inline-block;
91 | *display: inline;
92 | *zoom: 1;
93 | max-width: 100%;
94 | }
95 |
96 | /**
97 | * Prevent modern browsers from displaying `audio` without controls.
98 | * Remove excess height in iOS 5 devices.
99 | */
100 |
101 | audio:not([controls]) {
102 | display: none;
103 | height: 0;
104 | }
105 |
106 | /**
107 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
108 | * Known issue: no IE 6 support.
109 | */
110 |
111 | [hidden] {
112 | display: none;
113 | }
114 |
115 | /**
116 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
117 | * `em` units.
118 | * 2. Prevent iOS text size adjust after orientation change, without disabling
119 | * user zoom.
120 | */
121 |
122 | html {
123 | font-size: 100%; /* 1 */
124 | -webkit-text-size-adjust: 100%; /* 2 */
125 | -ms-text-size-adjust: 100%; /* 2 */
126 | }
127 |
128 | /**
129 | * Address `outline` inconsistency between Chrome and other browsers.
130 | */
131 |
132 | a:focus {
133 | outline: thin dotted;
134 | }
135 |
136 | /**
137 | * Improve readability when focused and also mouse hovered in all browsers.
138 | */
139 |
140 | a:active,
141 | a:hover {
142 | outline: 0;
143 | }
144 |
145 | /**
146 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
147 | * 2. Improve image quality when scaled in IE 7.
148 | */
149 |
150 | img {
151 | border: 0; /* 1 */
152 | -ms-interpolation-mode: bicubic; /* 2 */
153 | }
154 |
155 | /**
156 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
157 | */
158 |
159 | figure {
160 | margin: 0;
161 | }
162 |
163 | /**
164 | * Correct margin displayed oddly in IE 6/7.
165 | */
166 |
167 | form {
168 | margin: 0;
169 | }
170 |
171 | /**
172 | * Define consistent border, margin, and padding.
173 | */
174 |
175 | fieldset {
176 | border: 1px solid #c0c0c0;
177 | margin: 0 2px;
178 | padding: 0.35em 0.625em 0.75em;
179 | }
180 |
181 | /**
182 | * 1. Correct color not being inherited in IE 6/7/8/9.
183 | * 2. Correct text not wrapping in Firefox 3.
184 | * 3. Correct alignment displayed oddly in IE 6/7.
185 | */
186 |
187 | legend {
188 | border: 0; /* 1 */
189 | padding: 0;
190 | white-space: normal; /* 2 */
191 | *margin-left: -7px; /* 3 */
192 | }
193 |
194 | /**
195 | * 1. Correct font size not being inherited in all browsers.
196 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
197 | * and Chrome.
198 | * 3. Improve appearance and consistency in all browsers.
199 | */
200 |
201 | button,
202 | input,
203 | select,
204 | textarea {
205 | font-size: 100%; /* 1 */
206 | margin: 0; /* 2 */
207 | vertical-align: baseline; /* 3 */
208 | *vertical-align: middle; /* 3 */
209 | }
210 |
211 | /**
212 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in
213 | * the UA stylesheet.
214 | */
215 |
216 | button,
217 | input {
218 | line-height: normal;
219 | }
220 |
221 | /**
222 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
223 | * All other form control elements do not inherit `text-transform` values.
224 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
225 | * Correct `select` style inheritance in Firefox 4+ and Opera.
226 | */
227 |
228 | button,
229 | select {
230 | text-transform: none;
231 | }
232 |
233 | /**
234 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
235 | * and `video` controls.
236 | * 2. Correct inability to style clickable `input` types in iOS.
237 | * 3. Improve usability and consistency of cursor style between image-type
238 | * `input` and others.
239 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
240 | * Known issue: inner spacing remains in IE 6.
241 | */
242 |
243 | button,
244 | html input[type="button"], /* 1 */
245 | input[type="reset"],
246 | input[type="submit"] {
247 | -webkit-appearance: button; /* 2 */
248 | cursor: pointer; /* 3 */
249 | *overflow: visible; /* 4 */
250 | }
251 |
252 | /**
253 | * Re-set default cursor for disabled elements.
254 | */
255 |
256 | button[disabled],
257 | html input[disabled] {
258 | cursor: default;
259 | }
260 |
261 | /**
262 | * 1. Address box sizing set to content-box in IE 8/9.
263 | * 2. Remove excess padding in IE 8/9.
264 | * 3. Remove excess padding in IE 7.
265 | * Known issue: excess padding remains in IE 6.
266 | */
267 |
268 | input[type="checkbox"],
269 | input[type="radio"] {
270 | box-sizing: border-box; /* 1 */
271 | padding: 0; /* 2 */
272 | *height: 13px; /* 3 */
273 | *width: 13px; /* 3 */
274 | }
275 |
276 | /**
277 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
278 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
279 | * (include `-moz` to future-proof).
280 | */
281 |
282 | input[type="search"] {
283 | -webkit-appearance: textfield; /* 1 */
284 | -moz-box-sizing: content-box;
285 | -webkit-box-sizing: content-box; /* 2 */
286 | box-sizing: content-box;
287 | }
288 |
289 | /**
290 | * Remove inner padding and search cancel button in Safari 5 and Chrome
291 | * on OS X.
292 | */
293 |
294 | input[type="search"]::-webkit-search-cancel-button,
295 | input[type="search"]::-webkit-search-decoration {
296 | -webkit-appearance: none;
297 | }
298 |
299 | /**
300 | * Remove inner padding and border in Firefox 3+.
301 | */
302 |
303 | button::-moz-focus-inner,
304 | input::-moz-focus-inner {
305 | border: 0;
306 | padding: 0;
307 | }
308 |
309 | /**
310 | * 1. Remove default vertical scrollbar in IE 6/7/8/9.
311 | * 2. Improve readability and alignment in all browsers.
312 | */
313 |
314 | textarea {
315 | overflow: auto; /* 1 */
316 | vertical-align: top; /* 2 */
317 | }
318 |
319 | /**
320 | * Remove most spacing between table cells.
321 | */
322 |
323 | table {
324 | border-collapse: collapse;
325 | border-spacing: 0;
326 | }
327 |
328 | html,
329 | button,
330 | input,
331 | select,
332 | textarea {
333 | color: #222;
334 | }
335 |
336 |
337 | ::-moz-selection {
338 | background: #b3d4fc;
339 | text-shadow: none;
340 | }
341 |
342 | ::selection {
343 | background: #b3d4fc;
344 | text-shadow: none;
345 | }
346 |
347 | img {
348 | vertical-align: middle;
349 | }
350 |
351 | fieldset {
352 | border: 0;
353 | margin: 0;
354 | padding: 0;
355 | }
356 |
357 | textarea {
358 | resize: vertical;
359 | }
360 |
361 | .chromeframe {
362 | margin: 0.2em 0;
363 | background: #ccc;
364 | color: #000;
365 | padding: 0.2em 0;
366 | }
367 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | @import './mixins.scss';
2 | $primary-color: rgb(247,249,252);
3 | $secondary-color:#00807d;
4 | $primary-font: 'Work Sans', sans-serif;
5 | $secondary-font:'Roboto', sans-serif;
6 |
7 |
8 | //Card settings
9 | $card-padding :30px;
10 | $card-margin-bottom :30px;
11 | $card-border-width :0px;
12 | $card-border-color :#f6f7fb;
13 | $card-border-radious :8px;
14 | $card-box-shadow :1px 5px 24px 0 rgba(68,102,242,.05);
15 | $card-header-font-weight : 700;
16 | $card-header-bg-color : #fff;
17 | $card-header-font-size : 18px;
18 | $card-header-font-transform : uppercase;
19 | $card-header-font-color :#333;
20 | $card-header-span-size : 12px;
21 | $card-header-span-color :#333;
22 | $card-body-bg-color : transparent;
23 | $card-footer-bg-color : #fff;
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/utils/Index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import "react-loader-spinner/dist/loader/css/react-spinner-loader.css"
3 | import Loader from 'react-loader-spinner'
4 |
5 | //format order date
6 | export const formatOrderDate = date => {
7 | if(date) {
8 | const months = ["JAN", "FEB", "MAR","APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
9 | const dateSplitted = date.split("-")
10 | return `${ months[dateSplitted[1]] } ${ dateSplitted[2] }, ${ dateSplitted[0] }`
11 | } else {
12 | return
13 | }
14 | }
15 |
16 | // format money
17 | export const formatMoney = (amount, decimalCount = 2, decimal = ".", thousands = ",") => {
18 | try {
19 | decimalCount = Math.abs(decimalCount);
20 | decimalCount = isNaN(decimalCount) ? 2 : decimalCount;
21 | const negativeSign = amount < 0 ? "-" : "";
22 | let i = parseInt(amount = Math.abs(Number(amount) || 0).toFixed(decimalCount)).toString();
23 | let j = (i.length > 3) ? i.length % 3 : 0;
24 | return negativeSign + (j ? i.substr(0, j) + thousands : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "");
25 | } catch (e) {
26 | console.log(e)
27 | }
28 | };
29 |
30 |
31 | // loader
32 | export const Spinner = () => {
33 | return
40 | }
41 |
42 |
43 |
--------------------------------------------------------------------------------