├── src
├── assets
│ ├── font
│ │ ├── fontello.eot
│ │ ├── fontello.ttf
│ │ ├── fontello.woff
│ │ └── fontello.woff2
│ ├── login-bg.svg
│ ├── login-form.svg
│ ├── logo.svg
│ └── login-person.svg
├── types.js
├── reducers
│ ├── index.jsx
│ ├── categories.jsx
│ └── user.jsx
├── App.test.js
├── components
│ ├── Loading
│ │ ├── index.jsx
│ │ └── loading.scss
│ ├── Navbar
│ │ ├── index.jsx
│ │ └── navbar.scss
│ ├── Home
│ │ ├── home.scss
│ │ └── index.jsx
│ ├── Categories
│ │ ├── add.scss
│ │ ├── AddCategory.jsx
│ │ ├── index.jsx
│ │ ├── subcategory.scss
│ │ ├── SubCategory.jsx
│ │ ├── Category.jsx
│ │ └── categories.scss
│ ├── Products
│ │ ├── Product.jsx
│ │ └── index.jsx
│ ├── Orders
│ │ ├── orders.scss
│ │ ├── Success.jsx
│ │ ├── Shipping.jsx
│ │ ├── index.jsx
│ │ ├── New.jsx
│ │ ├── Process.jsx
│ │ ├── Order.jsx
│ │ └── new.scss
│ ├── Header
│ │ ├── index.jsx
│ │ └── header.scss
│ ├── Customers
│ │ ├── index.jsx
│ │ └── customers.scss
│ ├── Dashboard
│ │ ├── Bank.jsx
│ │ ├── dashboard.scss
│ │ └── index.jsx
│ ├── Login
│ │ ├── index.jsx
│ │ └── login.scss
│ ├── Admin
│ │ ├── admin.scss
│ │ └── index.jsx
│ └── Product
│ │ ├── index.jsx
│ │ ├── update.scss
│ │ ├── addproduct.scss
│ │ └── product.scss
├── config.js
├── index.js
├── actions
│ └── index.jsx
├── index.css
├── App.js
└── serviceWorker.js
├── static.json
├── public
├── images
│ └── icons
│ │ ├── icon-128x128.png
│ │ ├── icon-144x144.png
│ │ ├── icon-152x152.png
│ │ ├── icon-192x192.png
│ │ ├── icon-384x384.png
│ │ ├── icon-512x512.png
│ │ ├── icon-72x72.png
│ │ └── icon-96x96.png
├── manifest.json
└── index.html
├── .gitignore
├── package.json
└── README.md
/src/assets/font/fontello.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/src/assets/font/fontello.eot
--------------------------------------------------------------------------------
/src/assets/font/fontello.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/src/assets/font/fontello.ttf
--------------------------------------------------------------------------------
/src/assets/font/fontello.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/src/assets/font/fontello.woff
--------------------------------------------------------------------------------
/src/assets/font/fontello.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/src/assets/font/fontello.woff2
--------------------------------------------------------------------------------
/static.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": "build/",
3 | "routes": {
4 | "/**": "index.html"
5 | },
6 | "https_only": true
7 | }
--------------------------------------------------------------------------------
/public/images/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-128x128.png
--------------------------------------------------------------------------------
/public/images/icons/icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-144x144.png
--------------------------------------------------------------------------------
/public/images/icons/icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-152x152.png
--------------------------------------------------------------------------------
/public/images/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-192x192.png
--------------------------------------------------------------------------------
/public/images/icons/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-384x384.png
--------------------------------------------------------------------------------
/public/images/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-512x512.png
--------------------------------------------------------------------------------
/public/images/icons/icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-72x72.png
--------------------------------------------------------------------------------
/public/images/icons/icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aswara/Admin-eCommerce-ReactJS/HEAD/public/images/icons/icon-96x96.png
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | export const LOGIN = "LOGIN"
2 | export const LOGOUT = "LOGOUT"
3 |
4 | //categories types
5 | export const FETCH_CATEGORIES = "FETCH_CATEGORIES"
6 |
--------------------------------------------------------------------------------
/src/reducers/index.jsx:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import { categoriesReducer } from './categories'
3 | import { userReducer } from './user'
4 |
5 |
6 |
7 | export default combineReducers({ userReducer, categoriesReducer })
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/components/Loading/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './loading.scss'
3 |
4 | import logo from '../../assets/logo.svg'
5 |
6 | class index extends Component {
7 | render() {
8 | return (
9 |
10 |

11 |
loading
12 |
13 | );
14 | }
15 | }
16 |
17 | export default index;
--------------------------------------------------------------------------------
/src/reducers/categories.jsx:
--------------------------------------------------------------------------------
1 | import { FETCH_CATEGORIES } from '../types'
2 |
3 | const initialState = {
4 | data: []
5 | }
6 |
7 | export const categoriesReducer = ( state = initialState, action ) => {
8 | switch (action.type) {
9 | case FETCH_CATEGORIES:
10 | return {
11 | data: action.payload
12 | }
13 | default:
14 | return state
15 | }
16 | }
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/reducers/user.jsx:
--------------------------------------------------------------------------------
1 | import { LOGIN, LOGOUT } from '../types'
2 |
3 | const initialState = {
4 | token: localStorage.getItem('token'),
5 | user: localStorage.getItem('user')
6 | }
7 |
8 | export const userReducer = ( state = initialState, action ) => {
9 | switch (action.type) {
10 | case LOGIN:
11 | return {
12 | token: action.token,
13 | data: action.payload,
14 | login: action.login
15 | }
16 | case LOGOUT:
17 | return {
18 | token: action.token,
19 | data: action.payload,
20 | login: action.login
21 | }
22 | default:
23 | return state
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | //endpoint
4 | export const url = "https://e-commerce-rest-api.herokuapp.com"
5 |
6 | //headers
7 | export function headers(token) {
8 | return(
9 | {
10 | headers: {
11 | 'Authorization' : `Bearer ${token}`,
12 | 'Content-Type': 'application/json',
13 | 'Accept' : 'application/json'
14 | }
15 | }
16 | )
17 | }
18 |
19 | //convert price
20 | export function price(number){
21 | if(_.isNumber(number)){
22 | let reverse = number.toString().split('').reverse().join(''),
23 | thousand = reverse.match(/\d{1,3}/g);
24 | thousand = thousand.join('.').split('').reverse().join('');
25 | return thousand + ",-";
26 | } else {
27 | return number
28 | }
29 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import 'intersection-observer';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import './index.css';
5 | import App from './App';
6 | import * as serviceWorker from './serviceWorker';
7 |
8 | import reducers from './reducers'
9 | import { createStore, applyMiddleware } from 'redux'
10 | import thunk from 'redux-thunk'
11 | import { Provider } from 'react-redux'
12 |
13 | const store = createStore( reducers, applyMiddleware(thunk) )
14 |
15 | ReactDOM.render(
16 |
17 |
18 |
19 | , document.getElementById('root'));
20 |
21 | // If you want your app to work offline and load faster, you can change
22 | // unregister() to register() below. Note this comes with some pitfalls.
23 | // Learn more about service workers: http://bit.ly/CRA-PWA
24 | serviceWorker.register();
25 |
--------------------------------------------------------------------------------
/src/components/Loading/loading.scss:
--------------------------------------------------------------------------------
1 | .loading {
2 | text-align: center;
3 | position: fixed;
4 | width: 100%;
5 | height: 100%;
6 | left: 0;
7 | top: 0;
8 | background-color: rgba($color: #000000, $alpha: 0.5);
9 | z-index: 500;
10 |
11 | img {
12 | width: 20%;
13 | margin-top: 100px;
14 | animation: title 2s ease-in-out infinite;
15 | }
16 |
17 | h1 {
18 | font-weight: 400;
19 | color: #4694fc;
20 | animation: ecommerce 1.5s ease-in-out infinite;
21 | }
22 |
23 | @keyframes title {
24 | from {
25 | transform: rotate(0deg);
26 | }
27 | to {
28 | transform: rotate(360deg);
29 | }
30 | }
31 |
32 | @keyframes ecommerce {
33 | 0%{
34 | opacity: 0.2;
35 | }
36 | 50% {
37 | opacity: 1;
38 | }
39 | 100%{
40 | opacity: 0.2;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.18.0",
7 | "intersection-observer": "^0.5.1",
8 | "lodash": "^4.17.11",
9 | "react": "^16.6.3",
10 | "react-dom": "^16.6.3",
11 | "react-lazy": "^1.0.3",
12 | "react-quill": "^1.3.3",
13 | "react-redux": "^5.1.1",
14 | "react-router-dom": "^4.3.1",
15 | "react-scripts": "2.1.1",
16 | "redux": "^4.0.1",
17 | "redux-thunk": "^2.3.0"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": "react-app"
27 | },
28 | "browserslist": [
29 | ">0.2%",
30 | "not dead",
31 | "not ie <= 11",
32 | "not op_mini all"
33 | ],
34 | "devDependencies": {
35 | "node-sass": "^4.10.0",
36 | "sass-loader": "^7.1.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/Navbar/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './navbar.scss'
3 | import { NavLink } from 'react-router-dom'
4 |
5 | export default function index(props) {
6 | return (
7 |
8 | Dashboard
9 | Orders
10 | Products
11 | Customers
12 | Categories
13 |
14 | );
15 | }
--------------------------------------------------------------------------------
/src/components/Home/home.scss:
--------------------------------------------------------------------------------
1 | .home {
2 | text-align: center;
3 | position: fixed;
4 | width: 100%;
5 | height: 100%;
6 | left: 0;
7 | top: 0;
8 | background-color: #4694fc;
9 | z-index: 100;
10 |
11 | img {
12 | width: 20%;
13 | margin-top: 150px;
14 | animation: title 2s ease-in-out infinite;
15 | }
16 |
17 | h1 {
18 | font-weight: 500;
19 | color: white;
20 | animation: ecommerce 1.5s ease-in-out infinite;
21 | }
22 |
23 | @keyframes title {
24 | from {
25 | transform: rotate(0deg);
26 | }
27 | to {
28 | transform: rotate(360deg);
29 | }
30 | }
31 |
32 | @keyframes ecommerce {
33 | 0%{
34 | opacity: 0.2;
35 | }
36 | 50% {
37 | opacity: 1;
38 | }
39 | 100%{
40 | opacity: 0.2;
41 | }
42 | }
43 |
44 | span {
45 | color: red;
46 | }
47 | }
48 |
49 | @media (max-width: 700px){
50 | .home {
51 | img {
52 | width: 50%;
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/components/Categories/add.scss:
--------------------------------------------------------------------------------
1 | .add-category {
2 | margin-top: 10px;
3 | background-color: white;
4 | padding: 5px;
5 | box-shadow: 0 3px 10px lightgray;
6 | animation: add 1s ease-in-out;
7 | border-radius: 3px;
8 | width: 98%;
9 | margin-bottom: 10px;
10 |
11 | @keyframes add {
12 | 0%{
13 | opacity: 0;
14 | width: 0%;
15 | }
16 | 50%{
17 | width: 105%;
18 | }
19 | 100%{
20 | width: 99%;
21 | opacity: 1;
22 | }
23 | }
24 |
25 | form {
26 | display: flex;
27 | justify-content: space-between;
28 | }
29 |
30 | input {
31 | font-size: 14px;
32 | padding: 5px;
33 | border: 1px solid #87B7FB;
34 | background-color: #f5f5f5;
35 | border-radius: 3px;
36 | width: 92%;
37 | }
38 |
39 | button {
40 | background-color: #4694fc;
41 | border: none;
42 | color: white;
43 | border-radius: 3px;
44 | padding: 0 20px;
45 | cursor: pointer;
46 | margin-left: 10px;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/actions/index.jsx:
--------------------------------------------------------------------------------
1 | import { LOGIN,LOGOUT, FETCH_CATEGORIES } from '../types'
2 |
3 | export const userAction = (payload, login, token) => {
4 | return(dispatch) => {
5 | dispatch ({
6 | type: LOGIN,
7 | payload,
8 | login,
9 | token
10 | })
11 | }
12 | }
13 |
14 | export const loginAction = (token, payload) => {
15 | return (dispatch) => {
16 | dispatch({
17 | type: LOGIN,
18 | token : token,
19 | login : true,
20 | payload
21 | })
22 | }
23 | }
24 |
25 | export const logoutAction = () => {
26 | return (dispatch) => {
27 | localStorage.removeItem('token')
28 | localStorage.removeItem('user')
29 | dispatch({
30 | type: LOGOUT,
31 | token : null,
32 | login : false,
33 | payload: null
34 | })
35 | }
36 | }
37 |
38 | export const categoryAction = (payload) => {
39 | return (dispatch) => {
40 | localStorage.setItem('categories', JSON.stringify(payload) )
41 | dispatch({
42 | type: FETCH_CATEGORIES,
43 | payload
44 | })
45 |
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/Products/Product.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link } from 'react-router-dom'
3 | import { Lazy } from 'react-lazy'
4 | import { price } from '../../config'
5 |
6 | class Product extends Component {
7 |
8 | render() {
9 | const { product } = this.props
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {product.name}
20 |
21 |
22 |
Code product
23 |
{product.code}
24 |
25 |
26 |
Price
Rp {price(product.price)}
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 | export default Product;
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Admin e-Commerce",
3 | "short_name": "Admin eCommerce",
4 | "theme_color": "#5e8ffb",
5 | "background_color": "#4694fc",
6 | "display": "standalone",
7 | "orientation": "portrait",
8 | "Scope": "/",
9 | "start_url": "/index.html",
10 | "icons": [
11 | {
12 | "src": "images/icons/icon-72x72.png",
13 | "sizes": "72x72",
14 | "type": "image/png"
15 | },
16 | {
17 | "src": "images/icons/icon-96x96.png",
18 | "sizes": "96x96",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "images/icons/icon-128x128.png",
23 | "sizes": "128x128",
24 | "type": "image/png"
25 | },
26 | {
27 | "src": "images/icons/icon-144x144.png",
28 | "sizes": "144x144",
29 | "type": "image/png"
30 | },
31 | {
32 | "src": "images/icons/icon-152x152.png",
33 | "sizes": "152x152",
34 | "type": "image/png"
35 | },
36 | {
37 | "src": "images/icons/icon-192x192.png",
38 | "sizes": "192x192",
39 | "type": "image/png"
40 | },
41 | {
42 | "src": "images/icons/icon-384x384.png",
43 | "sizes": "384x384",
44 | "type": "image/png"
45 | },
46 | {
47 | "src": "images/icons/icon-512x512.png",
48 | "sizes": "512x512",
49 | "type": "image/png"
50 | }
51 | ],
52 | "splash_pages": null
53 | }
--------------------------------------------------------------------------------
/src/components/Orders/orders.scss:
--------------------------------------------------------------------------------
1 | .orders {
2 | padding-top: 50px;
3 | padding-left: 150px;
4 |
5 | .tab {
6 | margin-top: 5px;
7 | display: grid;
8 | grid-template-columns: 1fr 1fr 1fr 1fr;
9 | width: 100%;
10 | grid-gap: 15px;
11 | margin-bottom: 20px;
12 | animation: tab 1s ease-in-out;
13 |
14 | div {
15 | background-color: white;
16 | padding: 5px 0;
17 | text-align: center;
18 | box-shadow: 0 2px 3px lightgrey;
19 | cursor: pointer;
20 | border-radius: 20px;
21 | transition: 0.5s;
22 | }
23 |
24 | div:hover {
25 | background-color: #4694fc;
26 | color: white;
27 | box-shadow: 0 5px 10px lightgrey;
28 | }
29 |
30 | .active {
31 | background-color: #4694fc;
32 | color: white;
33 | }
34 |
35 | }
36 |
37 | @keyframes tab {
38 | 0%{
39 | opacity: 0;
40 | }
41 | 100%{
42 | opacity: 1;
43 | }
44 | }
45 |
46 | .wrapper {
47 | padding: 50px;
48 | color: #4694fc;
49 |
50 | }
51 | }
52 |
53 | @media (max-width: 700px){
54 | .orders {
55 | padding-top: 60px;
56 | padding-left: 0px;
57 | padding-bottom: 60px;
58 |
59 | .tab {
60 | grid-template-columns: 1fr 1fr;
61 | }
62 |
63 | .wrapper {
64 | padding: 10px;
65 |
66 |
67 | }
68 | }
69 | }
--------------------------------------------------------------------------------
/src/components/Categories/AddCategory.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './add.scss'
3 | import axios from 'axios'
4 | import { url, headers } from '../../config'
5 | import { connect } from 'react-redux'
6 |
7 | class AddCategory extends Component {
8 | state = {
9 | name : '',
10 | message : ''
11 | }
12 |
13 | handleChange = (e) => {
14 | this.setState({ [e.target.name] : e.target.value })
15 | }
16 |
17 | handleSubmit = (e) => {
18 | const { name } = this.state
19 | e.preventDefault()
20 |
21 | axios.post( url + '/category' , { name } , headers(this.props.user.token) )
22 | .then(res=>{
23 | this.props.update()
24 | this.setState({ name: '', message: 'Success add category' })
25 | })
26 | .catch(res=>{
27 | this.setState({ message: 'Failed add category' })
28 | })
29 | }
30 |
31 | render() {
32 | const { name, message } = this.state
33 | return (
34 |
35 |
36 |
40 |
41 |
{message}
42 |
43 | );
44 | }
45 | }
46 |
47 | const mapStateToProps = (state) => {
48 | return({
49 | user: state.userReducer
50 | })
51 | }
52 |
53 | export default connect(mapStateToProps)(AddCategory);
--------------------------------------------------------------------------------
/src/components/Orders/Success.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios'
3 | import { url, headers, price } from '../../config'
4 | import { connect } from 'react-redux'
5 | import _ from 'lodash'
6 | import './new.scss'
7 | import Order from './Order'
8 |
9 | class Success extends Component {
10 | state = {
11 | orders: [],
12 | loading: true,
13 | }
14 |
15 | componentDidMount(){
16 | this.fetchOrdersUnconfirmed()
17 | }
18 |
19 | fetchOrdersUnconfirmed(){
20 | let orders = localStorage.getItem('shippingsuccess')
21 | if(orders){
22 | this.setState({ orders: JSON.parse(orders), loading: false })
23 | }
24 | let token = this.props.user.token
25 | axios.get( url + "/order/show-success" , headers(token) )
26 | .then( res => {
27 | if(_.isArray(res.data.data)){
28 | localStorage.setItem("shippingsuccess", JSON.stringify(res.data.data))
29 | this.setState({ orders: res.data.data, loading: false })
30 | }
31 | })
32 | }
33 |
34 | render() {
35 | const { orders, loading } = this.state
36 | return (
37 |
38 |
39 | { loading ?
:
40 |
{ orders.length < 1 &&
Empty
} {
41 | orders.length > 0 && orders.map(order =>
42 |
43 | )
44 | }
45 | }
46 |
47 | );
48 | }
49 | }
50 |
51 | const mapStateToProps = (state) => {
52 | return({
53 | user : state.userReducer
54 | })
55 | }
56 |
57 | export default connect(mapStateToProps)(Success);
--------------------------------------------------------------------------------
/src/components/Orders/Shipping.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios'
3 | import { url, headers } from '../../config'
4 | import { connect } from 'react-redux'
5 | import _ from 'lodash'
6 | import './new.scss'
7 | import Order from './Order'
8 |
9 | class Shipping extends Component {
10 | state = {
11 | orders: [],
12 | loading: true,
13 | detail: false
14 | }
15 |
16 | componentDidMount(){
17 | this.fetchOrdersUnconfirmed()
18 | }
19 |
20 | fetchOrdersUnconfirmed(){
21 | let orders = localStorage.getItem('shippingorders')
22 | if(orders){
23 | this.setState({ orders: JSON.parse(orders), loading: false })
24 | }
25 | let token = this.props.user.token
26 | axios.get( url + "/order/shipping" , headers(token) )
27 | .then( res => {
28 | if(_.isArray(res.data.data)){
29 | localStorage.setItem("shippingorders", JSON.stringify(res.data.data))
30 | this.setState({ orders: res.data.data, loading: false })
31 | }
32 | })
33 | }
34 |
35 | render() {
36 | const { orders, loading } = this.state
37 | return (
38 |
39 |
40 | { loading ?
:
41 |
{ orders.length < 1 &&
Empty
} {
42 | orders.length > 0 && orders.map(order =>
43 |
44 | )
45 | }
46 | }
47 |
48 | );
49 | }
50 | }
51 |
52 | const mapStateToProps = (state) => {
53 | return({
54 | user : state.userReducer
55 | })
56 | }
57 |
58 | export default connect(mapStateToProps)(Shipping);
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | background-color: #F7F8F9;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
14 | monospace;
15 | }
16 |
17 | @font-face {
18 | font-family: 'fontello';
19 | src: url('./assets/font/fontello.eot?77699535');
20 | src: url('./assets/font/fontello.eot?77699535#iefix') format('embedded-opentype'),
21 | url('./assets/font/fontello.woff?77699535') format('woff'),
22 | url('./assets/font/fontello.ttf?77699535') format('truetype'),
23 | url('./assets/font/fontello.svg?77699535#fontello') format('svg');
24 | font-weight: normal;
25 | font-style: normal;
26 | }
27 |
28 | .demo-icon
29 | {
30 | font-family: "fontello";
31 | font-style: normal;
32 | font-weight: normal;
33 | speak: none;
34 |
35 | display: inline-block;
36 | text-decoration: inherit;
37 | width: 1em;
38 | margin-right: .2em;
39 | text-align: center;
40 | /* opacity: .8; */
41 |
42 | /* For safety - reset parent styles, that can break glyph codes*/
43 | font-variant: normal;
44 | text-transform: none;
45 |
46 | /* fix buttons height, for twitter bootstrap */
47 | line-height: 1em;
48 |
49 | /* Animation center compensation - margins should be symmetric */
50 | /* remove if not needed */
51 | margin-left: .2em;
52 |
53 | /* You can be more comfortable with increased icons size */
54 | /* font-size: 120%; */
55 |
56 | /* Font smoothing. That was taken from TWBS */
57 | -webkit-font-smoothing: antialiased;
58 | -moz-osx-font-smoothing: grayscale;
59 |
60 | /* Uncomment for 3D effect */
61 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
62 | }
--------------------------------------------------------------------------------
/src/components/Orders/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './orders.scss'
3 |
4 | import Header from '../Header'
5 | import Navbar from '../Navbar'
6 | import New from './New';
7 | import Shipping from './Shipping'
8 | import Success from './Success'
9 | import Process from './Process'
10 |
11 | class index extends Component {
12 | state = {
13 | tab: "new"
14 | }
15 |
16 | render() {
17 | const { tab } = this.state
18 | return (
19 |
20 |
21 |
22 |
23 |
Orders
24 |
25 |
26 |
this.setState({ tab: "new" })}>
27 | New
28 |
29 |
this.setState({ tab: "process" })}>
30 | Process
31 |
32 |
this.setState({ tab: "shipping" })}>
33 | Shipping
34 |
35 |
this.setState({ tab: "success" })}>
36 | Success
37 |
38 |
39 |
40 |
41 | {
42 | tab === "new" &&
43 | }
44 | {
45 | tab === "process" &&
46 | }
47 | {
48 | tab === "shipping" &&
49 | }
50 | {
51 | tab === "success" &&
52 | }
53 |
54 |
55 |
56 | );
57 | }
58 | }
59 |
60 | export default index;
--------------------------------------------------------------------------------
/src/components/Home/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './home.scss'
3 | import { userAction } from '../../actions'
4 | import { connect } from 'react-redux'
5 | import { url, headers } from '../../config'
6 | import axios from 'axios'
7 |
8 | import logo from '../../assets/logo.svg'
9 |
10 | class index extends Component {
11 | state = {
12 | message: ''
13 | }
14 |
15 | componentDidMount() {
16 | let token = this.props.user.token
17 | let user = this.props.user.user
18 | if(navigator.onLine){
19 | axios.get( url + "/admin/profile" , headers(token) )
20 | .then(res=>{
21 | if(res.data){
22 | localStorage.setItem('token', token)
23 | localStorage.setItem('user', JSON.stringify(res.data.data))
24 | this.props.userAction(res.data.data, true, token)
25 | this.props.history.push("/dashboard")
26 | }
27 | })
28 | .catch(err=>{
29 | if(err.response){
30 | this.props.userAction(null , false, null)
31 | this.props.history.push("/login")
32 | } else {
33 | this.props.userAction(null , false, null)
34 | this.setState({ message: 'Failed connect server' })
35 | }
36 | })
37 | } else {
38 | if(user && token){
39 | this.props.userAction(JSON.parse(user), true, token)
40 | this.props.history.push("/dashboard")
41 | } else {
42 | this.props.history.push("/login")
43 | this.props.userAction(user, false, token)
44 | }
45 | }
46 |
47 | }
48 |
49 | render() {
50 | const { message } = this.state
51 | return (
52 |
53 |

54 |
Hello Admin
55 |
{message}
56 |
57 | );
58 | }
59 | }
60 |
61 | const mapStateToProps = (state) => {
62 | return({
63 | user: state.userReducer
64 | })
65 | }
66 |
67 | export default connect(mapStateToProps, { userAction }) (index);
--------------------------------------------------------------------------------
/src/components/Header/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './header.scss'
3 | import { connect } from 'react-redux'
4 | import { Link } from 'react-router-dom'
5 | import { logoutAction } from '../../actions'
6 |
7 | class index extends Component {
8 | state={
9 | dropdown: true,
10 | data: {
11 | user: '',
12 | photo: '',
13 | name: '',
14 | email: ''
15 | }
16 | }
17 |
18 |
19 | componentDidMount(){
20 |
21 | let data = this.props.user.data
22 | if(data){
23 | this.setState({ data })
24 | }
25 | }
26 |
27 | dropDown = () => {
28 | this.setState({ dropdown: !this.state.dropdown })
29 | }
30 |
31 | render() {
32 | const { dropdown, data } = this.state
33 | return (
34 |
35 |
36 | e-Commerce
37 |
38 |
39 |
{data.name} {dropdown ? : }
40 |
41 |
42 |

43 |
{data.name}
44 |
{data.email}
45 |
account
46 |
this.props.logoutAction() } className="logout"> logout
47 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
54 | const mapStateToProps = (state) => {
55 | return {
56 | user: state.userReducer
57 | }
58 | }
59 |
60 | export default connect(mapStateToProps,{ logoutAction })(index);
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BrowserRouter, Switch, Route } from 'react-router-dom'
3 | import { connect } from 'react-redux'
4 |
5 | import Home from './components/Home'
6 | import Login from './components/Login'
7 | import Dashboard from './components/Dashboard'
8 | import Orders from './components/Orders'
9 | import Products from './components/Products'
10 | import Product from './components/Product'
11 | import Categories from './components/Categories'
12 | import AddProduct from './components/Product/AddProduct'
13 | import UpdateProduct from './components/Product/Update'
14 | import Customers from './components/Customers'
15 | import Admin from './components/Admin'
16 |
17 | class App extends Component {
18 | componentDidMount() {
19 | const element = document.getElementById('startingLoader')
20 | window.onload = () => {
21 | if(element) {
22 | element.remove()
23 | }
24 | }
25 | }
26 |
27 | render() {
28 | const { user } = this.props
29 | return (
30 |
31 | {
32 | user.login ?
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | :
48 |
49 |
50 |
51 |
52 |
53 | }
54 |
55 | );
56 | }
57 | }
58 |
59 | const mapStateToProps = (state) => {
60 | return({
61 | user: state.userReducer
62 | })
63 | }
64 |
65 | export default connect(mapStateToProps)(App);
66 |
--------------------------------------------------------------------------------
/src/components/Customers/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './customers.scss'
3 | import axios from 'axios'
4 | import { url, headers } from '../../config'
5 | import { connect } from 'react-redux'
6 |
7 | import Header from '../Header'
8 | import Navbar from '../Navbar'
9 |
10 | class index extends Component {
11 | state = {
12 | customers : [1,2,3,4,5,6,7,8,9],
13 | loading : true,
14 | }
15 |
16 | componentDidMount() {
17 | let customers = JSON.parse(localStorage.getItem('customers'))
18 | if(customers){
19 | this.setState({ customers, loading: false })
20 | }
21 |
22 | axios.get( url + "/customers" , headers(this.props.user.token) )
23 | .then(res=>{
24 | if( res.data.constructor === Array )
25 | localStorage.setItem('customers', JSON.stringify(res.data))
26 | this.setState({ customers: res.data, loading: false })
27 | })
28 | }
29 |
30 | render() {
31 | const { customers, loading } = this.state
32 | return (
33 |
34 |
35 |
36 |
37 |
Customers
38 |
39 | {
40 | loading ? customers.map((val, i)=>
) :
41 | customers.map(customer=>{
42 | return(
43 |
44 |
45 |

46 |
{customer.name}
47 |
48 |
{customer.phone}
49 |
{customer.email}
50 |
51 | )
52 | })
53 | }
54 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 |
62 | const mapStateToProps = (state) => {
63 | return({
64 | user: state.userReducer
65 | })
66 | }
67 |
68 | export default connect(mapStateToProps)(index);
--------------------------------------------------------------------------------
/src/components/Orders/New.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios'
3 | import { url, headers} from '../../config'
4 | import _ from 'lodash'
5 | import { connect } from 'react-redux'
6 | import './new.scss'
7 |
8 | import Order from './Order'
9 |
10 | class New extends Component {
11 | state = {
12 | orders: [],
13 | loading: true,
14 | message: '',
15 | }
16 |
17 | componentDidMount(){
18 | this.fetchOrdersUnconfirmed()
19 | }
20 |
21 | fetchOrdersUnconfirmed(){
22 | let orders = localStorage.getItem('neworders')
23 | if(orders){
24 | this.setState({ orders: JSON.parse(orders), loading: false })
25 | }
26 | let token = this.props.user.token
27 | axios.get( url + "/order/unconfirmed" , headers(token) )
28 | .then( res => {
29 | if(_.isArray(res.data.data)){
30 | localStorage.setItem("neworders", JSON.stringify(res.data.data))
31 | this.setState({ orders: res.data.data, loading: false })
32 | }
33 | })
34 | }
35 |
36 | confirm = (id) => {
37 | this.setState({ loading: true })
38 | let token = this.props.user.token
39 | axios.get( url + "/order/confirm?order_id=" + id , headers(token) )
40 | .then(res=>{
41 | this.setState({ message: 'Success confirm order', loading: false })
42 | this.fetchOrdersUnconfirmed()
43 | })
44 | .catch(err=>{
45 | this.setState({ message: 'Failed confirm order', loading: false })
46 | })
47 | }
48 |
49 | render() {
50 | const { orders, loading, message } = this.state
51 | return (
52 |
53 |
{message}
54 | { loading ?
:
55 |
{ orders.length < 1 &&
Empty
} {
56 | orders.length > 0 && orders.map(order =>
57 |
58 | )
59 | }
60 | }
61 |
62 | );
63 | }
64 | }
65 |
66 | const mapStateToProps = (state) => {
67 | return({
68 | user : state.userReducer
69 | })
70 | }
71 |
72 | export default connect(mapStateToProps)(New);
--------------------------------------------------------------------------------
/src/components/Customers/customers.scss:
--------------------------------------------------------------------------------
1 | .customers {
2 | padding-top: 50px;
3 | padding-left: 150px;
4 |
5 | .wrapper {
6 | padding: 50px;
7 | color: #4694fc;
8 |
9 | .loading-customers {
10 | height: 40px;
11 | width: 100%;
12 | border-radius: 30px;
13 | background-color: white;
14 | margin-top: 10px;
15 | animation: loading-customers 1.5s ease-in-out infinite;
16 | }
17 |
18 | @keyframes loading-customers {
19 | 0%{
20 | opacity: 0.4;
21 | }
22 | 50%{
23 | opacity: 0.9;
24 | }
25 | 100%{
26 | opacity: 0.4;
27 | }
28 | }
29 |
30 | .customer {
31 | margin-top: 10px;
32 | display: grid;
33 | grid-template-columns: 2fr 1fr 1fr;
34 | background-color: white;
35 | padding: 5px;
36 | color: #505050;
37 | border-radius: 30px;
38 | align-items: center;
39 | box-shadow: 0 3px 2px lightgray;
40 | animation: customer 1s ease-in-out;
41 |
42 | .photo {
43 | display: flex;
44 | align-items: center;
45 |
46 | img {
47 | background-color: lightgray;
48 | height: 30px;
49 | width: 30px;
50 | margin-right: 10px;
51 | border-radius: 50%;
52 | }
53 |
54 | span {
55 | font-weight: 500;
56 | }
57 | }
58 |
59 | }
60 |
61 | @keyframes customer {
62 | 0%{
63 | margin-top: -50px;
64 | opacity: 0;
65 | }
66 | 100%{
67 | margin-top: 10px;
68 | opacity: 1;
69 | }
70 | }
71 |
72 | }
73 | }
74 |
75 | @media (max-width: 700px){
76 | .customers {
77 | padding-left: 0px;
78 | padding-top: 60px;
79 | padding-bottom: 60px;
80 |
81 | .wrapper {
82 | padding: 10px;
83 |
84 | .customer {
85 | padding: 10px;
86 | border-radius: 20px;
87 |
88 | display: grid;
89 | grid-template-columns: 1fr;
90 |
91 | .photo {
92 | margin-bottom: 5px;
93 | }
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/src/components/Orders/Process.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios'
3 | import { url, headers } from '../../config'
4 | import { connect } from 'react-redux'
5 | import _ from 'lodash'
6 | import './new.scss'
7 | import Order from './Order'
8 |
9 | class Process extends Component {
10 | state = {
11 | orders: [],
12 | loading: true,
13 | detail: false,
14 | message: ''
15 | }
16 |
17 | componentDidMount(){
18 | this.fetchOrdersProcess()
19 | }
20 |
21 | fetchOrdersProcess(){
22 | let orders = localStorage.getItem('orderprocess')
23 | if(orders){
24 | this.setState({ orders: JSON.parse(orders), loading: false })
25 | }
26 | let token = this.props.user.token
27 | axios.get( url + "/order/waiting-shipping" , headers(token) )
28 | .then( res => {
29 | if(_.isArray(res.data.data)){
30 | localStorage.setItem("orderprocess", JSON.stringify(res.data.data))
31 | this.setState({ orders: res.data.data, loading: false })
32 | }
33 | })
34 | }
35 |
36 | sendAwb = (order_id, awb) => {
37 | const token = this.props.user.token
38 |
39 | if( awb && order_id ){
40 | this.setState({loading: true})
41 | axios.post( url + "/order/confirm-shipping?order_id="+order_id, { awb } , headers(token) )
42 | .then(res=>{
43 | this.setState({ message: 'Success confirm process shipping', loading: false })
44 | this.fetchOrdersProcess()
45 | })
46 | .catch(err=>{
47 | this.setState({ message: 'Failed confirm process shipping', loading: false })
48 | })
49 | }
50 | }
51 |
52 | render() {
53 | const { orders, loading, message } = this.state
54 | return (
55 |
56 |
{message}
57 | { loading ?
:
58 |
{ orders.length < 1 &&
Empty
} {
59 | orders.length > 0 && orders.map(order =>
60 |
61 | )
62 | }
63 | }
64 |
65 | );
66 | }
67 | }
68 |
69 | const mapStateToProps = (state) => {
70 | return({
71 | user : state.userReducer
72 | })
73 | }
74 |
75 | export default connect(mapStateToProps)(Process);
--------------------------------------------------------------------------------
/src/components/Header/header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | top: 0;
3 | position: fixed;
4 | width: 100%;
5 | background-color: #4694fc;
6 | color: white;
7 | height: 60px;
8 | display: flex;
9 | z-index: 10;
10 | left: 0;
11 | right: 0;
12 | box-shadow: 0px 3px 5px lightgray;
13 | justify-content: space-between;
14 | align-items: center;
15 |
16 | .title {
17 | margin-left: 12px;
18 | font-size: 22px;
19 | font-weight: 500;
20 | }
21 |
22 | .admin {
23 | margin-right: 18px;
24 |
25 | span {
26 | cursor: pointer;
27 | }
28 |
29 | .dropdown {
30 | width: 150px;
31 | height: auto;
32 | position: absolute;
33 | background-color: white;
34 | color: gray;
35 | padding: 15px;
36 | margin-top: 10px;
37 | right: 25px;
38 | border-radius: 20px;
39 | box-shadow: 0px 3px 3px lightgray;
40 | display: flex;
41 | flex-direction: column;
42 | animation: admin 1s ease-in-out;
43 | text-align: center;
44 |
45 | img {
46 | width: 130px;
47 | height: 130px;
48 | border-radius: 50%;
49 | margin-left: 11px;
50 | background-color: lightgray;
51 | }
52 |
53 | .name {
54 | margin-top: 10px;
55 | font-size: 16px;
56 | font-weight: 500;
57 | color: #4694fc;
58 | }
59 |
60 | .email {
61 | margin-top: 5px;
62 | font-size: 13px;
63 | }
64 |
65 | .logout {
66 | margin-top: 15px;
67 | text-align: center;
68 | background-color: #F6535A;
69 | padding: 5px 5px;
70 | color: white;
71 | border-radius: 20px;
72 | box-shadow: 0px 5px 10px lightgray;
73 | transition: 0.5s;
74 | }
75 |
76 | .logout:hover {
77 | box-shadow: 0px 10px 10px lightgray;
78 | }
79 |
80 | @keyframes admin {
81 | 0%{
82 | top: -500px;
83 | }
84 | 50%{
85 | top: 80px
86 | }
87 | 100%{
88 | top: 40px;
89 | }
90 | }
91 |
92 | }
93 |
94 | .hide {
95 | display: none;
96 | }
97 | }
98 |
99 | }
--------------------------------------------------------------------------------
/src/components/Categories/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './categories.scss'
3 | import axios from 'axios'
4 | import _ from 'lodash'
5 | import { url } from '../../config'
6 | import { connect } from 'react-redux'
7 | import { categoryAction } from '../../actions'
8 |
9 | import Header from '../Header'
10 | import Navbar from '../Navbar'
11 | import AddCategory from './AddCategory'
12 | import Category from './Category'
13 |
14 | class index extends Component {
15 | state = {
16 | new_category: true,
17 | categories: [1,2,3,4,5,6,7,8],
18 | loading: true,
19 | }
20 |
21 | componentDidMount(){
22 | let category = localStorage.getItem('categories')
23 | if(_.isString(category)){
24 | let categories = JSON.parse(category)
25 | if(_.isArray(categories))
26 | this.setState({ categories, loading: false })
27 | }
28 | this.fetchCategories()
29 | }
30 |
31 | fetchCategories = () => {
32 | axios.get( url + '/category' )
33 | .then(res=>{
34 | if(_.isArray(res.data.data)){
35 | this.props.categoryAction(res.data.data)
36 | this.setState({ categories: res.data.data, loading: false })
37 | }
38 | })
39 | }
40 |
41 | newCategory = () => {
42 | this.setState({ new_category: !this.state.new_category })
43 | }
44 |
45 | render() {
46 | const { new_category, categories, loading } = this.state
47 | return (
48 |
49 |
50 |
51 |
52 | { //loading fect data categories
53 | loading ?
{ categories.map(category=>{ return(
) }) }
:
54 |
55 |
56 |
57 |
Categories
58 |
59 | {/* Button for add category */}
60 |
61 | { !new_category ? : }
62 |
63 |
64 | {/* Component for input new category */}
65 | { new_category ? '' :
}
66 |
67 | {/* List all categories */}
68 | {
69 | categories.map(category=>{
70 | return(
71 |
72 | )
73 | })
74 | }
75 |
76 |
77 |
78 | }
79 |
80 |
81 | );
82 | }
83 | }
84 |
85 | const mapStateToProps = (state) => {
86 | return({
87 | category: state. categoriesReducer
88 | })
89 | }
90 |
91 | export default connect(mapStateToProps, { categoryAction })(index);
--------------------------------------------------------------------------------
/src/components/Categories/subcategory.scss:
--------------------------------------------------------------------------------
1 | .sub-category {
2 | display: flex;
3 | flex-direction: column;
4 | background-color: white;
5 | padding: 10px;
6 | color: gray;
7 | border-radius: 3px;
8 | box-shadow: 0 3px 3px lightgray;
9 |
10 | .photo {
11 | min-height: 190px;
12 | background-color: white;
13 | margin-bottom: 5px;
14 | }
15 |
16 | img {
17 | min-height: 190px;
18 | height: 100%;
19 | width: 100%;
20 | }
21 |
22 | .name {
23 | font-size: 16px;
24 | font-weight: 500;
25 | margin-bottom: 5px;
26 | }
27 |
28 |
29 | .actions {
30 | display: grid;
31 | grid-template-columns: 1fr 1fr;
32 | grid-gap: 8px;
33 |
34 | .update {
35 | height: 20px;
36 | color: white;
37 | padding: 1px 0;
38 | border-radius: 3px;
39 | background-color: #4DB649;
40 | box-shadow: 0px 2px 10px lightgray;
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | font-size: 13px;
45 | cursor: pointer;
46 | }
47 |
48 | .update:hover {
49 | box-shadow: 0px 10px 20px lightgray;
50 | }
51 |
52 | .delete {
53 | height: 20px;
54 | color: white;
55 | padding: 1px 5px;
56 | border-radius: 3px;
57 | background-color: #ff5555;
58 | box-shadow: 0px 2px 10px lightgray;
59 | display: flex;
60 | justify-content: center;
61 | align-items: center;
62 | font-size: 13px;
63 | cursor: pointer;
64 | }
65 |
66 | .delete:hover {
67 | box-shadow: 0px 10px 20px lightgray;
68 | }
69 | }
70 | }
71 |
72 | .update-subcategory {
73 | margin-top: 10px;
74 | background-color: white;
75 | padding: 5px;
76 | box-shadow: 0 3px 10px lightgray;
77 | animation: add 1s ease-in-out;
78 | border-radius: 3px;
79 | width: 99%;
80 | margin-bottom: 10px;
81 |
82 | @keyframes add {
83 | 0%{
84 | opacity: 0;
85 | width: 0%;
86 | }
87 | 50%{
88 | width: 105%;
89 | }
90 | 100%{
91 | width: 99%;
92 | opacity: 1;
93 | }
94 | }
95 |
96 | form {
97 | display: flex;
98 | justify-content: space-between;
99 | }
100 |
101 | input {
102 | font-size: 14px;
103 | padding: 5px;
104 | border: 1px solid #87B7FB;
105 | background-color: #f5f5f5;
106 | border-radius: 3px;
107 | width: 93%;
108 | margin-right: 5px;
109 | margin-top: 5px;
110 | transition: 1s;
111 | }
112 |
113 | button {
114 | margin-top: 5px;
115 | background-color: #4694fc;
116 | border: none;
117 | color: white;
118 | border-radius: 3px;
119 | padding: 0 10px;
120 | cursor: pointer;
121 | }
122 | }
123 |
124 | @media (max-width: 700px){
125 | .sub-category {
126 | .actions {
127 | display: grid;
128 | grid-template-columns: 1fr;
129 | }
130 |
131 | .photo {
132 | height: auto;
133 | min-height: 140px;
134 | }
135 |
136 | img {
137 | min-height: 140px;
138 | height: auto;
139 | }
140 | }
141 | }
--------------------------------------------------------------------------------
/src/assets/login-bg.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Admin e-Commerce ReactJS
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 | #### Live Preview : https://admin-ecommerce.herokuapp.com/
5 | - username : adi
6 | - password : 12345
7 |
8 | ## Required
9 | - Internet to access this website
10 |
11 | ## Admin Features (Fitur - Fitur Admin)
12 | - Admin can add, edit, and delete product (Admin bisa menambahahkan,Mengedit dan Menghapus Produk)
13 | - Admin can add, edit, and delete category (Admin bisa menambahahkan,Mengedit dan Menghapus Kategori)
14 | - Admin can add, edit, and delete subcategory (Admin bisa menambahahkan,Mengedit dan Menghapus Sub Kategori)
15 | - Admin can view list customers (Admin dapat melihat semua Kustomer)
16 | - Admin can view list products (Admin dapat melihat semua Produk)
17 | - Admin can view list and confirm new order (Admin dapat melihat list dan meneriman konfirmasi order)
18 | - Admin can view list order in shipping (Admin dapat melihat semua order di dalam pembelian)
19 |
20 | ## Featured Tech
21 | - PWA - Progressive Web Application
22 | - Add to home screen
23 | - Offline support. Thanks to service workers.
24 | - Lazy Loading Image
25 | - Responsive Web
26 | - State Management using Redux
27 | - Utility Library using Lodash
28 | - XMLHttpRequest using axios
29 | - Rich Text Editor using React-Quill
30 | - CSS preprocessor using Sass
31 |
32 | ## Backend
33 | - Programming language PHP
34 | - Framework Lumen
35 | - https://e-commerce-rest-api.herokuapp.com
36 |
37 | ## Setup
38 |
39 | 1. Clone the repo
40 | 2. `$ cd Admin-eCommerce-ReactJS
41 | 3. Run `$ yarn` or `npm install`
42 | 4. Run locally `$ yarn start` or `npm start`
43 |
44 |
45 | ## Available Scripts
46 |
47 | In the project directory, you can run:
48 |
49 | ### `npm start`
50 |
51 | Runs the app in the development mode.
52 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
53 |
54 | The page will reload if you make edits.
55 | You will also see any lint errors in the console.
56 |
57 | ### `npm test`
58 |
59 | Launches the test runner in the interactive watch mode.
60 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
61 |
62 | ### `npm run build`
63 |
64 | Builds the app for production to the `build` folder.
65 | It correctly bundles React in production mode and optimizes the build for the best performance.
66 |
67 | The build is minified and the filenames include the hashes.
68 | Your app is ready to be deployed!
69 |
70 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
71 |
72 | ### `npm run eject`
73 |
74 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
75 |
76 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
77 |
78 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
79 |
80 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
81 |
82 | ## Learn More
83 |
84 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
85 |
86 | To learn React, check out the [React documentation](https://reactjs.org/).
87 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | Admin e-Commerce
23 |
24 |
92 |
93 |
94 |
97 |
98 |
99 |
105 |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/src/components/Navbar/navbar.scss:
--------------------------------------------------------------------------------
1 | .navbar {
2 | width: 150px;
3 | height: 100%;
4 | color: red;
5 | background-color: white;
6 | position: fixed;
7 | top: 60px;
8 | left: 0px;
9 | display: flex;
10 | flex-direction: column;
11 | padding-top: 70px;
12 | box-shadow: 1px 0 5px lightgrey;
13 | transition: 1s;
14 | z-index: 200;
15 |
16 | .link {
17 | margin-bottom: 55px;
18 | color: #4694fc;
19 | text-decoration: none;
20 | transition: 0.5s;
21 | }
22 |
23 | .link:hover {
24 | color: white;
25 | width: 100%;
26 | background-image: linear-gradient(to left, #5E80FC,#80befc);
27 | box-shadow: 0 10px 20px lightgray;
28 | padding: 10px 0px;
29 | font-size: 18px;
30 | font-weight: 500;
31 | border-radius: 0 0px 0px 0;
32 | }
33 |
34 | .active {
35 | color: white;
36 | width: 100%;
37 | background-image: linear-gradient(to left, #5E80FC,#80befc);
38 | box-shadow: 0 10px 20px lightgray;
39 | padding: 10px 0px;
40 | font-size: 18px;
41 | font-weight: 500;
42 | border-radius: 0 0px 0px 0;
43 | animation: active 0.2s linear;
44 | }
45 |
46 | @keyframes active {
47 | 0%{
48 | transform: scaleX(0)
49 | }
50 | 50%{
51 | transform: scaleX(0.5)
52 | }
53 | 100%{
54 | transform: scaleX(1)
55 | }
56 | }
57 |
58 | i {
59 | margin-right: 10px;
60 | margin-left: 15px;
61 |
62 | }
63 | }
64 |
65 | @media (max-width: 700px){
66 | .navbar {
67 | width: 100%;
68 | flex-direction: row;
69 | top: auto;
70 | left: 0;
71 | bottom: 0;
72 | height: 50px;
73 | justify-content: space-around;
74 | padding-top: 0px;
75 |
76 | span {
77 | display: none;
78 | }
79 |
80 | .link {
81 | padding-top: 13px;
82 | width: auto;
83 | margin-bottom: 0px;
84 | padding-left: 0;
85 | width: auto;
86 | font-size: 20px;
87 | }
88 |
89 | .link:hover {
90 | color: white;
91 | width: 60px;
92 | padding: 0px 0px;
93 | height: 60px;
94 | background-image: linear-gradient( #5E80FC,#5E80FC);
95 | box-shadow: 0px 5px 5px lightgrey;
96 | font-size: 27px;
97 | border-radius: 50%;
98 | margin-top: -15px;
99 | display: flex;
100 | align-items: center;
101 | align-content: center;
102 | }
103 |
104 | .active {
105 | border-radius: 50%;
106 | background-color: #4694fc;
107 | width: 60px;
108 | margin-top: -15px;
109 | display: flex;
110 | align-items: center;
111 | align-content: center;
112 | height: 60px;
113 | padding: 0px 0px;
114 | background-image: linear-gradient( #5E80FC,#5E80FC);
115 | box-shadow: 0px 5px 5px lightgrey;
116 | color: white;
117 | font-size: 27px;
118 | animation: active 0.5s ease-in-out;
119 |
120 | }
121 |
122 | @keyframes active {
123 | 0%{
124 | transform: scale(1)
125 | }
126 | 50%{
127 | transform: scale(1.2)
128 | }
129 | 100%{
130 | transform: scale(1)
131 | }
132 | }
133 |
134 | i {
135 | margin-left: 15px;
136 | margin-right: 15px;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/components/Dashboard/Bank.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios'
3 | import { url, headers } from '../../config'
4 | import { connect } from 'react-redux'
5 |
6 | class Bank extends Component {
7 | state = {
8 | action : false,
9 | edit: false,
10 | message: ''
11 | }
12 |
13 | componentDidMount(){
14 | const { bank } = this.props
15 | this.setState({
16 | account_name : bank.account_name,
17 | account_number: bank.account_number,
18 | bank_name: bank.bank_name,
19 | bank_id: bank.bank_id
20 | })
21 | }
22 |
23 | deleteBank = (id) => {
24 | this.props.update(id)
25 | this.setState({
26 | action: false
27 | })
28 | }
29 |
30 | handleChange = (e) => {
31 | this.setState({
32 | [e.target.name] : e.target.value
33 | })
34 | }
35 |
36 | handleSubmit = (e) => {
37 | e.preventDefault()
38 | const {account_name, account_number, bank_name, bank_id} = this.state
39 | if(account_name && account_number && bank_name){
40 | const data = {
41 | account_name,
42 | account_number,
43 | bank_name
44 | }
45 | axios.put( url + "/bank/" + bank_id , data , headers(this.props.user.token) )
46 | .then(res =>{
47 | this.setState({ message: "Success update Bank", edit: false, action: false })
48 | this.props.update()
49 | })
50 | .catch(err =>{
51 | this.setState({ message: "Failed update Bank", edit: false, action: false })
52 | })
53 | }
54 | }
55 |
56 | render() {
57 | const { message, action, edit, account_name, account_number, bank_name } = this.state
58 | const { bank } = this.props
59 | return (
60 |
61 | {
62 | edit ?
63 |
64 | Edit Bank
65 |
71 |
72 | :
73 |
74 |
75 |
this.setState({action: !action})}>...
76 | {
77 | action &&
78 |
79 |
80 |
81 |
82 | }
83 |
84 |
{message}
85 |
86 | Bank name
87 | {bank.bank_name}
88 |
89 |
90 | Account name
91 | {bank.account_name}
92 |
93 |
94 | Account number
95 | {bank.account_number}
96 |
97 |
98 | Description
99 | {bank.description}
100 |
101 |
102 | }
103 |
104 | );
105 | }
106 | }
107 |
108 | const mapStateToProps = (state) => {
109 | return({
110 | user: state.userReducer
111 | })
112 | }
113 |
114 | export default connect(mapStateToProps)(Bank);
--------------------------------------------------------------------------------
/src/components/Login/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './login.scss'
3 | import axios from 'axios'
4 | import { connect } from 'react-redux'
5 | import { url } from '../../config'
6 | import { loginAction } from '../../actions'
7 | import { Redirect } from 'react-router-dom'
8 | import Loading from '../Loading'
9 |
10 | import imageform from '../../assets/login-form.svg'
11 | import imgBackground from '../../assets/login-bg.svg'
12 | import imgPerson from '../../assets/login-person.svg'
13 | import screen1 from '../../assets/login-screen1.svg'
14 | import screen2 from '../../assets/login-screen2.svg'
15 | import screen3 from '../../assets/login-screen3.svg'
16 | import imgIcons from '../../assets/login-icons.svg'
17 |
18 |
19 | class index extends Component {
20 | state = {
21 | loading : false,
22 | error: '',
23 | username: null,
24 | password: null,
25 | }
26 |
27 | handleChange = (e) => {
28 | this.setState({ [e.target.name] : e.target.value })
29 | }
30 |
31 | handleSubmit = (e) => {
32 | e.preventDefault()
33 | const { username, password, loading } = this.state
34 |
35 | if(!username){ this.setState({ error: 'Enter your username' }) }
36 | if(!password){ this.setState({ error: 'Enter your password' }) }
37 | if(!username && !password){ this.setState({ error: 'Enter your username and password' }) }
38 |
39 | let formData = new FormData()
40 | formData.append('username', username)
41 | formData.append('password', password)
42 |
43 | if( password && username ) {
44 | this.setState({ loading: true })
45 | this.setState({ error: '' })
46 |
47 | axios.post( url + "/admin/login" , formData )
48 | .then(res=>{
49 | if(res.data) {
50 | this.props.loginAction(res.data.token, res.data.data)
51 | localStorage.setItem('token', res.data.token)
52 | localStorage.setItem('user', JSON.stringify(res.data.data))
53 | }
54 | })
55 | .catch(err=>{
56 | if(err.response){
57 | this.setState({ loading: false })
58 | this.setState({ error: 'Wrong username or password' })
59 | } else {
60 | this.setState({ loading: false })
61 | this.setState({ error: 'Failed connect server' })
62 | }
63 |
64 | })
65 | }
66 | }
67 |
68 | render() {
69 | const { loading, error } = this.state
70 | return (
71 |
72 |
73 | { loading ?
: '' }
74 | { this.props.user.login ?
: '' }
75 |
76 |
77 |

78 |

79 |

80 |

81 |

82 |

83 |
84 |
85 |

86 |
101 |
102 |
103 | );
104 | }
105 | }
106 |
107 |
108 | const mapStateToProps = (state) => {
109 | return({
110 | user: state.userReducer
111 | })
112 | }
113 |
114 | export default connect(mapStateToProps, {loginAction})(index);
--------------------------------------------------------------------------------
/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 http://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 | // const swUrl = `${process.env.PUBLIC_URL}/sw.js`;
37 |
38 | if (isLocalhost) {
39 | // This is running on localhost. Let's check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl, config);
41 |
42 | // Add some additional logging to localhost, pointing developers to the
43 | // service worker/PWA documentation.
44 | navigator.serviceWorker.ready.then(() => {
45 | console.log(
46 | 'This web app is being served cache-first by a service ' +
47 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
48 | );
49 | });
50 | } else {
51 | // Is not localhost. Just register service worker
52 | registerValidSW(swUrl, config);
53 | }
54 | });
55 | }
56 | }
57 |
58 | function registerValidSW(swUrl, config) {
59 | navigator.serviceWorker
60 | .register(swUrl)
61 | .then(registration => {
62 | registration.onupdatefound = () => {
63 | const installingWorker = registration.installing;
64 | if (installingWorker == null) {
65 | return;
66 | }
67 | installingWorker.onstatechange = () => {
68 | if (installingWorker.state === 'installed') {
69 | if (navigator.serviceWorker.controller) {
70 | // At this point, the updated precached content has been fetched,
71 | // but the previous service worker will still serve the older
72 | // content until all client tabs are closed.
73 | console.log(
74 | 'New content is available and will be used when all ' +
75 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
76 | );
77 |
78 | // Execute callback
79 | if (config && config.onUpdate) {
80 | config.onUpdate(registration);
81 | }
82 | } else {
83 | // At this point, everything has been precached.
84 | // It's the perfect time to display a
85 | // "Content is cached for offline use." message.
86 | console.log('Content is cached for offline use.');
87 |
88 | // Execute callback
89 | if (config && config.onSuccess) {
90 | config.onSuccess(registration);
91 | }
92 | }
93 | }
94 | };
95 | };
96 | })
97 | .catch(error => {
98 | console.error('Error during service worker registration:', error);
99 | });
100 | }
101 |
102 | function checkValidServiceWorker(swUrl, config) {
103 | // Check if the service worker can be found. If it can't reload the page.
104 | fetch(swUrl)
105 | .then(response => {
106 | // Ensure service worker exists, and that we really are getting a JS file.
107 | const contentType = response.headers.get('content-type');
108 | if (
109 | response.status === 404 ||
110 | (contentType != null && contentType.indexOf('javascript') === -1)
111 | ) {
112 | // No service worker found. Probably a different app. Reload the page.
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister().then(() => {
115 | window.location.reload();
116 | });
117 | });
118 | } else {
119 | // Service worker found. Proceed as normal.
120 | registerValidSW(swUrl, config);
121 | }
122 | })
123 | .catch(() => {
124 | console.log(
125 | 'No internet connection found. App is running in offline mode.'
126 | );
127 | });
128 | }
129 |
130 | export function unregister() {
131 | if ('serviceWorker' in navigator) {
132 | navigator.serviceWorker.ready.then(registration => {
133 | registration.unregister();
134 | });
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/components/Admin/admin.scss:
--------------------------------------------------------------------------------
1 | .account {
2 | padding-top: 50px;
3 | padding-left: 150px;
4 |
5 | .wrapper {
6 | padding: 50px;
7 | color: #505050;
8 | font-size: 18px;
9 | animation: account 1s ease-in-out;
10 |
11 | .change {
12 | position: fixed;
13 | height: 100%;
14 | width: 100%;
15 | top: 0;
16 | left: 0;
17 | z-index: 202;
18 | background-color: rgba($color: #000000, $alpha: 0.5);
19 |
20 | form {
21 | background-color: white;
22 | width: 300px;
23 | display: flex;
24 | flex-direction: column;
25 | padding: 20px;
26 | border-radius: 10px;
27 | position: relative;
28 | margin: 100px auto;
29 | animation: change 0.5s ease-in-out;
30 |
31 | h2 {
32 | margin-top: 0;
33 | font-weight: 500;
34 | }
35 |
36 | label {
37 | font-size: 16px;
38 | }
39 |
40 | input {
41 | margin-bottom: 15px;
42 | font-size: 18px;
43 | padding: 5px 7px;
44 | border: 1px solid #4bcffa;
45 | border-radius: 3px;
46 | }
47 |
48 | button {
49 | font-size: 18px;
50 | cursor: pointer;
51 | padding: 3px;
52 | background-image: linear-gradient(#5daafd, #4bcffa);
53 | border: none;
54 | color: white;
55 | border-radius: 3px;
56 | box-shadow: 0px 3px 20px lightgrey;
57 | }
58 |
59 | .edit {
60 | top: 5px;
61 | font-size: 35px;
62 | right: 5px;
63 | color: #5D8EFB;
64 | cursor: pointer;
65 | position: absolute;
66 | }
67 | }
68 |
69 | @keyframes change {
70 | 0%{
71 | margin-top: -300px;
72 | }
73 | 100%{
74 | margin-top: 100px;
75 | }
76 | }
77 | }
78 |
79 | .profile {
80 | border-radius: 10px;
81 | box-shadow: 0px 3px 3px lightgray;
82 | background-color: white;
83 | height: auto;
84 | max-width: 350px;
85 | margin: 10px auto;
86 | padding: 20px;
87 | padding-bottom: 50px;
88 | position: relative;
89 |
90 | .edit {
91 | top: 15px;
92 | font-size: 35px;
93 | right: 15px;
94 | color: #5D8EFB;
95 | cursor: pointer;
96 | position: absolute;
97 | }
98 |
99 | input {
100 | padding: 2px 5px;
101 | border: none;
102 | border-bottom: 1px solid lightblue;
103 | font-size: 18px;
104 | outline: none;
105 | width: 100%;
106 | }
107 |
108 | button {
109 | border: none;
110 | color: white;
111 | border-radius: 20px;
112 | outline: none;
113 | cursor: pointer;
114 | font-size: 18px;
115 | background-image: linear-gradient(#5daafd, #4bcffa);
116 | padding: 3px 0;
117 | }
118 |
119 | button:hover {
120 | box-shadow: 0 5px 10px lightgray;
121 | }
122 |
123 | .photo {
124 | width: 200px;
125 | height: 200px;
126 | border-radius: 50%;
127 | margin: 10px auto;
128 | background-color: lightgray;
129 |
130 | img {
131 | height: 100%;
132 | width: 100%;
133 | border-radius: 50%;
134 | }
135 | }
136 |
137 | .username {
138 | display: grid;
139 | grid-template-columns: 1fr 2fr;
140 | padding: 10px;
141 | border-bottom: 1px solid lightgray;
142 | }
143 |
144 | .name {
145 | display: grid;
146 | grid-template-columns: 1fr 2fr;
147 | padding: 10px;
148 | border-bottom: 1px solid lightgray;
149 | }
150 |
151 | .email {
152 | display: grid;
153 | grid-template-columns: 1fr 2fr;
154 | padding: 10px;
155 | border-bottom: 1px solid lightgray;
156 | }
157 | }
158 |
159 | }
160 |
161 | @keyframes account {
162 | 0%{
163 | opacity: 0;
164 | transform: scaleY(0);
165 | }
166 | 100%{
167 | transform: scaleY(1);
168 | opacity: 1;
169 | }
170 | }
171 | }
172 |
173 | @media (max-width: 700px){
174 | .account {
175 | padding-top: 60px;
176 | padding-left: 0px;
177 | padding-bottom: 60px;
178 |
179 | .wrapper {
180 | padding: 10px;
181 |
182 | .profile {
183 | width: 85%;
184 | }
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/src/components/Categories/SubCategory.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './subcategory.scss'
3 | import axios from 'axios'
4 | import { url, headers } from '../../config'
5 | import { connect } from 'react-redux'
6 |
7 | class SubCategory extends Component {
8 | state = {
9 | open : true,
10 | comfirm_delete : true,
11 | name : '',
12 | message : ''
13 | }
14 |
15 |
16 | deleteCategory(id) {
17 | axios.delete( url+ '/subcategory/' + id , headers(this.props.user.token) )
18 | .then(res=>{
19 | this.setState({ comfirm_delete: true, message: 'Success delete subcategory' })
20 | this.props.update()
21 | })
22 | .catch(err=>{
23 | this.setState({ comfirm_delete: true, message: 'Failed delete subcategory' })
24 | })
25 | }
26 |
27 |
28 | openUpdateCategory = (name) =>{
29 | this.setState({ name, open: !this.state.open, message: '' })
30 | }
31 |
32 | handleImage = (e) => {
33 | let file = e.target.files[0]
34 | let reader = new FileReader()
35 |
36 | reader.readAsDataURL(file)
37 |
38 | reader.onloadend = () => {
39 | this.setState({
40 | file: file,
41 | image: reader.result
42 | })
43 | }
44 | }
45 |
46 | updateCategory(e, id) {
47 | e.preventDefault()
48 | const { name, file, image } = this.state
49 | let formData = new FormData()
50 | formData.append('image', file)
51 |
52 | //post data if no change image
53 | if(name && !file){
54 | let data = {
55 | name,
56 | image: this.props.data.icon,
57 | category_id: this.props.data.category_id
58 | }
59 | axios.put( url + '/subcategory/' + id , data ,headers(this.props.user.token) )
60 | .then(res=>{
61 | this.setState({ message: 'Success update subcategory', open: true })
62 | this.props.update()
63 | })
64 | .catch(err=>{ this.setState({ message: 'Failed update subcategory' }) })
65 | }
66 |
67 | //post data if change image
68 | if(name && file){
69 | axios.post( url + "/subcategory/upload-image", formData )
70 | .then(res=>{
71 | let data = {
72 | name,
73 | image: res.data.url,
74 | category_id: this.props.data.category_id
75 | }
76 | axios.put( url + '/subcategory/' + id , data ,headers(this.props.user.token) )
77 | .then(res=>{
78 | this.setState({ message: 'Success update subcategory', open: true })
79 | this.props.update()
80 | })
81 | .catch(err=>{ this.setState({ message: 'Failed update subcategory' }) })
82 | })
83 | }
84 |
85 |
86 | }
87 |
88 | render() {
89 | const { open, name, comfirm_delete, message, image } = this.state
90 | const { data } = this.props
91 | return (
92 |
93 |
{message}
94 |
95 |
96 | { image ?

:

}
97 |
98 |
99 | {data.name}
100 |
101 |
102 |
{this.setState({ comfirm_delete : false})}} className="delete">
103 | delete
104 |
105 |
{this.openUpdateCategory(data.name)}} className="update">
106 | edit
107 |
108 |
109 |
110 |
111 | { //open comfirm delete category
112 | comfirm_delete ? '' :
113 |
114 |
115 |
Are you sure want to delete?
116 |
117 |
118 |
119 |
120 | }
121 |
122 |
123 | { //open input for update category
124 | open ? '' :
125 |
126 |
127 |
131 |
132 | }
133 |
134 |
135 | );
136 | }
137 | }
138 |
139 | const mapStateToProps = (state) => {
140 | return({
141 | user: state.userReducer
142 | })
143 | }
144 |
145 | export default connect(mapStateToProps)(SubCategory);
--------------------------------------------------------------------------------
/src/components/Dashboard/dashboard.scss:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | padding-top: 50px;
3 | padding-left: 150px;
4 |
5 | .wrapper {
6 | padding: 50px;
7 |
8 | span {
9 | color: #4694fc;
10 | }
11 |
12 | .total {
13 | margin-top: 10px;
14 | display: flex;
15 | justify-content: space-between;
16 | margin-bottom: 20px;
17 |
18 | i {
19 | margin-top: 10px;
20 | margin-bottom: 10px;
21 | color: #4694fc;
22 | font-size: 90px;
23 | }
24 |
25 | .card {
26 | cursor: pointer;
27 | background-color: white;
28 | width: 30%;
29 | border-radius: 17px;
30 | box-shadow: 0px 3px 5px lightgrey;
31 | display: flex;
32 | animation: card 1s ease-in-out;
33 | transition: 0.5s;
34 |
35 | div {
36 | width: 50%;
37 | text-align: center;
38 |
39 | p {
40 | margin-top: 10px;;
41 | color:gray;
42 | margin-bottom: 10px;
43 | font-size: 15px;
44 | }
45 |
46 | h1 {
47 | margin-top: 0px;
48 | color: #4694fc;
49 | font-weight: 400;
50 | font-size: 50px;
51 | margin-bottom: 10px;
52 | }
53 | }
54 | }
55 |
56 | .card:hover {
57 | box-shadow: 0 10px 50px lightgray;
58 | width: 35%;
59 | }
60 |
61 | @keyframes card {
62 | 0%{
63 | opacity: 0;
64 | margin-top: -100px
65 | }
66 | 100%{
67 | opacity: 1;
68 | margin-top: 0
69 | }
70 | }
71 | }
72 |
73 | button {
74 | background-color: #4694fc;
75 | font-size: 14px;
76 | padding: 3px 20px;
77 | border: none;
78 | color: white;
79 | cursor: pointer;
80 | border-radius: 2px;
81 | }
82 |
83 | .form-bank {
84 | margin-top: 10px;
85 | display: flex;
86 | flex-direction: column;
87 | margin-bottom: 10px;
88 |
89 | input {
90 | margin-bottom: 10px;
91 | font-size: 14px;
92 | padding: 5px 5px;
93 | border: 1px solid lightblue;
94 | }
95 | }
96 |
97 | .load {
98 | div {
99 | width: 100%;
100 | height: 50px;
101 | background-color: lightgray;
102 | margin-bottom: 10px;
103 | animation: load 2s infinite;
104 | border-radius: 20px;
105 |
106 | }
107 |
108 | @keyframes laod {
109 | 0%{
110 | opacity: 0.2;
111 | }
112 | 50%{
113 | opacity: 0.9;
114 | }
115 | 100%{
116 | opacity: 0.2;
117 | }
118 | }
119 |
120 | }
121 |
122 | .bank {
123 | background-color: white;
124 | margin-top: 10px;
125 | padding: 10px;
126 | box-shadow: 0px 3px 5px lightgrey;
127 | border-radius: 3px;
128 | position: relative;
129 |
130 | div {
131 | display: grid;
132 | grid-template-columns: 1fr 2fr;
133 | padding: 5px;
134 | grid-gap: 10px;
135 | color: #4694fc;
136 |
137 | span {
138 | color: #505050;
139 | }
140 | }
141 |
142 | .action {
143 | position: absolute;
144 | right: 0px;
145 |
146 | span {
147 | top: -20px;
148 | right: 13px;
149 | color: gray;
150 | cursor: pointer;
151 | font-weight: 600;
152 | font-size: 30px;
153 | position: absolute;
154 | }
155 |
156 | button {
157 | position: absolute;
158 | top: 20px;
159 | right: 10px;
160 | animation: button 1s ease-in-out;
161 | }
162 |
163 | @keyframes button {
164 | 0%{
165 | opacity: 0;
166 | transform: scale(0.2);
167 | }
168 | 50%{
169 | transform: scale(1.2);
170 | }
171 | 100%{
172 | opacity: 1;
173 | transform: scale(1);
174 | }
175 | }
176 | }
177 | }
178 |
179 | }
180 | }
181 |
182 | @media (max-width: 700px){
183 | .dashboard {
184 | padding-bottom: 60px;
185 | padding-left: 0px;
186 | padding-top: 60px;
187 |
188 | .wrapper {
189 | padding: 10px;
190 |
191 | .total {
192 | flex-wrap: wrap;
193 |
194 | .card {
195 | width: 100%;
196 | margin-bottom: 20px;
197 | }
198 |
199 | .card:hover {
200 | box-shadow: 0 10px 50px lightgray;
201 | width: 100%;
202 | }
203 | }
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/components/Login/login.scss:
--------------------------------------------------------------------------------
1 | .login {
2 |
3 | .wrapper {
4 | background-color: white;
5 | width: 310px;
6 | height: 385px;
7 | border-radius: 15px;
8 | box-shadow: 0px 2px 0px lightgray;
9 | position: absolute;
10 | right: 15%;
11 | top: 110px;
12 | z-index: 50;
13 | animation: form 2s ease-in-out;
14 |
15 | img {
16 | width: 100%;
17 | position: absolute;
18 | }
19 |
20 | .error {
21 | color: #c83737;
22 | position: absolute;
23 | top: 13px;
24 | }
25 |
26 | form {
27 | margin-top: -116px;
28 | margin-left: 25px;
29 | margin-right: 25px;
30 | position: relative;
31 | }
32 |
33 | @keyframes form {
34 | 0%{
35 | top: 0;
36 | opacity: 0;
37 | }
38 | 100%{
39 | top:110px;
40 | opacity: 1;
41 | }
42 | }
43 |
44 | h2 {
45 | margin-top: 230px;
46 | color: #5daafd;
47 | font-weight: 400;
48 | margin-bottom: 20px;
49 | }
50 |
51 | label {
52 | color: #5daafd;
53 | position: absolute;
54 | }
55 |
56 | input {
57 | width: 91%;
58 | padding: 5px 10px;
59 | margin-top: 7px;
60 | margin-bottom: 10px;
61 | border: 1px solid rgb(190, 215, 252);
62 | font-size: 16px;
63 | background-color: #eaf4ff;
64 | border-radius: 3px;
65 | outline: none;
66 | box-shadow: 0 3px 3px lightgray;
67 | transition: 0.5s;
68 | position: absolute;
69 | }
70 | input:focus {
71 | transform: scale(1.1);
72 | background-color: white;
73 | }
74 |
75 | #username {
76 | top: 70px;
77 | }
78 |
79 |
80 | #password {
81 | top: 140px;
82 | }
83 |
84 |
85 | button {
86 | width: 260px;
87 | font-size: 16px;
88 | background-image: linear-gradient(#5daafd, #4bcffa);
89 | border: none;
90 | color: white;
91 | padding: 7px;
92 | border-radius: 3px;
93 | box-shadow: 0px 3px 20px lightgrey;
94 | transition: 0.5s;
95 | position: absolute;
96 | top: 200px;
97 | animation: button 1.5s ease-in;
98 | }
99 |
100 | @keyframes button {
101 | 0%{
102 | top: 0;
103 | opacity: 0;
104 | }
105 | 50%{
106 | top: 220px
107 | }
108 | 100%{
109 | opacity: 1;
110 | top: 200px;
111 | }
112 | }
113 |
114 |
115 | button:hover {
116 |
117 | transform: scale(1.1);
118 | cursor: pointer;
119 | }
120 | }
121 |
122 | .left {
123 | .background {
124 | position: fixed;
125 | width: auto;
126 | height: 100%;
127 | left: 0;
128 | top: 0;
129 | z-index: 1;
130 | }
131 |
132 | .person {
133 | z-index: 5;
134 | position: fixed;
135 | height: 40%;
136 | top: 45%;
137 | left: 15%;
138 | }
139 |
140 | .screen1 {
141 | top: 15%;
142 | left: 23%;
143 | z-index: 2;
144 | height: 30%;
145 | position: fixed;
146 | animation: screen1 2s ease-in-out infinite;
147 | }
148 |
149 | @keyframes screen1 {
150 | 0%{
151 | top: 15%;
152 | }
153 | 50%{
154 | top: 10%;
155 | }
156 | 100% {
157 | top: 15%;
158 | }
159 | }
160 |
161 | .screen2 {
162 | top: 20%;
163 | left: 13%;
164 | z-index: 2;
165 | height: 15%;
166 | position: fixed;
167 | animation: screen2 1.5s ease-in-out infinite;
168 | }
169 |
170 | @keyframes screen2 {
171 | 0%{
172 | top: 25%;
173 | }
174 | 50%{
175 | top: 30%;
176 | }
177 | 100% {
178 | top: 25%;
179 | }
180 | }
181 |
182 | .screen3 {
183 | top: 40%;
184 | left: 25%;
185 | z-index: 2;
186 | height: 25%;
187 | position: fixed;
188 | animation: screen3 2s ease-in-out infinite;
189 | }
190 |
191 | @keyframes screen3 {
192 | 0%{
193 | top: 43%;
194 | }
195 | 50%{
196 | top: 48%;
197 | }
198 | 100% {
199 | top: 43%;
200 | }
201 | }
202 |
203 | .icons {
204 | position: fixed;
205 | z-index: 2;
206 | height:70%;
207 | top: 8%;
208 | left: 6%;
209 | animation: icons 2s ease-in-out;
210 | }
211 |
212 | @keyframes icons {
213 | 0%{
214 | top:-50%;
215 | opacity: 0;
216 | }
217 | 75%{
218 | top:15%;
219 | }
220 | 100%{
221 | top: 8%;
222 | opacity: 1;
223 | }
224 | }
225 |
226 | }
227 |
228 | }
229 |
230 | // responsive mobile
231 | @media (max-width: 700px) {
232 | .login {
233 | display: flex;
234 | justify-content: center;
235 |
236 | .wrapper {
237 | position: relative;
238 | left: auto;
239 | right: auto;
240 | }
241 | }
242 |
243 |
244 | }
--------------------------------------------------------------------------------
/src/assets/login-form.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
145 |
--------------------------------------------------------------------------------
/src/components/Orders/Order.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import _ from 'lodash'
3 | import { price, url, headers } from '../../config'
4 | import { connect } from 'react-redux'
5 | import { Link } from 'react-router-dom'
6 |
7 | class Order extends Component {
8 | state = {
9 | detail: false,
10 | input: false,
11 | awb: '',
12 | order_id: null
13 | }
14 |
15 | confirm(id){
16 | this.props.confirm(id)
17 | }
18 |
19 | confirmProcess(id){
20 | this.setState({ order_id: id, input: true })
21 | }
22 |
23 | sendAwb = (e) => {
24 | e.preventDefault()
25 | const { awb, order_id } = this.state
26 | this.props.sendAwb(order_id, awb)
27 | this.setState({input: false})
28 | }
29 |
30 | render() {
31 | const { detail, input } = this.state
32 | const { order, status } = this.props
33 | return (
34 |
35 | { input &&
36 |
37 |
38 | this.setState({ awb: e.target.value })} type="text"/>
39 |
40 |
41 |
42 |
}
43 | {
44 | detail ?
45 |
this.setState({ detail: false })}>
46 | { status === "new" &&
this.confirm(order.order_id)} className="confirm">Confirm
}
47 | { status === "process" &&
this.confirmProcess(order.order_id)} className="confirm">Shipping
}
48 |
Detail Order
49 |
50 |
51 |
52 | Received Name
53 | {order.shipping_info.received_name}
54 |
55 |
56 | Phone
57 | {order.shipping_info.phone}
58 |
59 |
60 | Province
61 | {order.shipping_info.province_name}
62 |
63 |
64 | City
65 | {order.shipping_info.city_name}
66 |
67 |
68 | Postal Code
69 | {order.shipping_info.zip}
70 |
71 |
72 | Address
73 | {order.shipping_info.address}
74 |
75 |
76 |
77 |
78 | Status
79 | {order.status}
80 |
81 |
82 | Date
83 | {order.due_date}
84 |
85 |
86 | Invoice
87 | {order.invoice}
88 |
89 |
90 | Amount
91 | Rp {price(order.amount)}
92 |
93 |
94 | Shipping Cost
95 | Rp {price(order.shipping_cost)}
96 |
97 |
98 | Total Payment
99 | Rp {price(order.total_payment)}
100 |
101 |
102 |
103 |
104 |
105 |
Products
106 | {
107 | _.isArray(order.order_detail) && order.order_detail.map( product =>
108 |
109 |
110 |
{product.product_name}
111 |
Size {product.size}
112 |
Rp { price(product.price) }
113 |
114 |
115 | )
116 | }
117 |
118 |
119 | :
120 |
121 |
this.setState({ detail: true })} className="hide">
122 | {order.shipping_info.received_name}
123 | Total : {order.order_detail.length} product
124 | Payment : Rp {price(order.total_payment)}
125 | Date : {order.due_date}
126 |
127 | }
128 |
129 | );
130 | }
131 | }
132 |
133 | const mapStateToProps = (state) => {
134 | return({
135 | user : state.userReducer
136 | })
137 | }
138 |
139 | export default connect(mapStateToProps)(Order);
--------------------------------------------------------------------------------
/src/components/Dashboard/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './dashboard.scss'
3 | import axios from 'axios'
4 | import { url, headers } from '../../config'
5 | import { connect } from 'react-redux'
6 | import Header from '../Header'
7 | import Navbar from '../Navbar'
8 | import Bank from './Bank'
9 |
10 | class index extends Component {
11 | state = {
12 | total : {
13 | customers: '',
14 | products: '',
15 | unconfirmed_orders: ''
16 | },
17 | banks : [],
18 | loading: true,
19 | add: false,
20 | bank_name: '',
21 | account_name: '',
22 | account_number: '',
23 | message: ''
24 | }
25 |
26 | componentDidMount(){
27 | this.fetchTotal()
28 | this.fetchBank()
29 | }
30 |
31 | fetchTotal = () => {
32 | let total = localStorage.getItem('total')
33 | if(total){
34 | this.setState({ total: JSON.parse(total), loading: false })
35 | }
36 |
37 | axios.get( url + "/count" , headers(this.props.user.token) )
38 | .then(res=>{
39 | if( res.data )
40 | localStorage.setItem('total', JSON.stringify(res.data))
41 | this.setState({ total: res.data, loading: false })
42 | })
43 | }
44 |
45 | fetchBank = () => {
46 | let banks = localStorage.getItem('banks')
47 | if(banks){
48 | this.setState({ banks: JSON.parse(banks), loading: false })
49 | }
50 |
51 | axios.get( url + "/bank" , headers(this.props.user.token) )
52 | .then(res=>{
53 | if( res.data )
54 | localStorage.setItem('banks', JSON.stringify(res.data))
55 | this.setState({ banks: res.data, loading: false })
56 | })
57 | }
58 |
59 | updateBank = (id) => {
60 | if(id){
61 | axios.delete( url + "/bank/" + id , headers(this.props.user.token) )
62 | .then(res=>{
63 | this.fetchBank()
64 | this.setState({ message: "Success delete Bank" })
65 | })
66 | .catch(err=>{
67 | this.setState({ message: "Failed delete Bank" })
68 | })
69 | } else {
70 | this.fetchBank()
71 | }
72 | }
73 |
74 | handleChange = (e) => {
75 | this.setState({
76 | [e.target.name] : e.target.value
77 | })
78 | }
79 |
80 | handleSubmit = (e) => {
81 | e.preventDefault()
82 | const {account_name, account_number, bank_name} = this.state
83 | if(account_name && account_number && bank_name){
84 | const data = {
85 | account_name,
86 | account_number,
87 | bank_name
88 | }
89 | axios.post( url + "/bank" , data , headers(this.props.user.token) )
90 | .then(res =>{
91 | this.setState({ message: "Success add Bank", add: false })
92 | this.fetchBank()
93 | })
94 | .catch(err =>{
95 | this.setState({ message: "Failed add Bank", add: false })
96 | })
97 | }
98 | }
99 |
100 | render() {
101 | const { total, banks, loading, add, message } = this.state
102 | return (
103 |
104 |
105 |
106 |
107 |
108 |
Overview
109 |
110 |
this.props.history.push("/orders")} className="card">
111 |
112 |
113 |
Recent Orders
114 |
{total.unconfirmed_orders}
115 |
116 |
117 |
this.props.history.push("/products")} className="card">
118 |
119 |
120 |
Total Products
121 |
{total.products}
122 |
123 |
124 |
this.props.history.push("/customers")} className="card">
125 |
126 |
127 |
Total Costumers
128 |
{total.customers}
129 |
130 |
131 |
132 |
My Bank
133 | { add ?
:
}
134 | { add &&
135 |
141 |
}
142 |
143 |
{message}
144 | {
145 | loading ?
:
146 | banks.map( bank =>
147 |
148 | )
149 | }
150 |
151 |
152 |
153 | );
154 | }
155 | }
156 |
157 | const mapStateToProps = (state) => {
158 | return({
159 | user: state.userReducer
160 | })
161 | }
162 |
163 | export default connect(mapStateToProps)(index);
--------------------------------------------------------------------------------
/src/components/Product/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './product.scss'
3 | import axios from 'axios'
4 | import { url, headers, price } from '../../config'
5 | import { connect } from 'react-redux'
6 | import { Link } from 'react-router-dom'
7 | import _ from 'lodash'
8 |
9 | import Header from '../Header'
10 | import Navbar from '../Navbar'
11 |
12 | class index extends Component {
13 | state = {
14 | product : '',
15 | comfirm_delete: true,
16 | message: '',
17 | loading: true
18 | }
19 |
20 | deleteProduct(id) {
21 | axios.delete( url+ '/product/' + id , headers(this.props.user.token) )
22 | .then(res=>{
23 | this.props.history.push("/products")
24 | })
25 | .catch(err=>{
26 | this.setState({ message: 'Failed delete', comfirm_delete: false })
27 | })
28 | }
29 |
30 | componentDidMount(){
31 | if(this.props.location.state){
32 | this.setState({
33 | product: this.props.location.state,
34 | loading: false
35 | })
36 | }
37 | else {
38 |
39 | let id = this.props.match.params.id
40 | axios( url + "/product/" + id )
41 | .then(res=>{
42 | if(res.data){
43 | this.setState({
44 | product: res.data,
45 | loading: false
46 | })
47 | }
48 | })
49 | }
50 | }
51 |
52 |
53 | render() {
54 | const { product, comfirm_delete, message, loading } = this.state
55 | return (
56 |
57 |
58 |
59 |
60 | { //open comfirm delete category
61 | comfirm_delete ? '' :
62 |
63 |
64 |
Are you sure want to delete?
65 |
66 |
67 |
68 |
69 | }
70 |
71 |
72 | Products { ' > ' + product.name}
73 |
74 |
75 |
76 |
{this.setState({ comfirm_delete: false })}} className="delete">
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | { loading ?
:
91 |

}
92 |
93 |
94 |
{message}
95 |
96 |
97 |
98 | {product.name}
99 |
100 |
101 | Code Product
102 | {product.code}
103 |
104 |
105 |
106 | Category
107 | {product.category_name}
108 |
109 |
110 | Subcategory
111 | {product.sub_category_name}
112 |
113 |
114 |
115 | Weight
116 | {product.weight} gram
117 |
118 |
119 |
120 |
121 |
Size
122 |
123 | {
124 | _.isArray(product.sizes) && product.sizes.map( (x,i) =>
{x}
)
125 | }
126 |
127 |
128 |
Stock
129 |
130 | {
131 | _.isArray(product.stocks) && product.stocks.map( (x,i) =>
{x}
)
132 | }
133 |
134 |
135 |
136 |
137 |
138 |
139 | Price
140 | Rp {price(product.price)}
141 |
142 |
143 |
144 |
145 | Description
146 |
147 |
148 |
149 |
150 |
151 | );
152 | }
153 | }
154 |
155 |
156 | const mapStateToProps = (state) => {
157 | return({
158 | user: state.userReducer
159 | })
160 | }
161 |
162 | export default connect(mapStateToProps)(index);
--------------------------------------------------------------------------------
/src/components/Product/update.scss:
--------------------------------------------------------------------------------
1 | .update-product {
2 | padding-top: 60px;
3 | padding-left: 150px;
4 |
5 | .cancel {
6 | height: 50px;
7 | width: 50px;
8 | border-radius: 25px;
9 | position: fixed;
10 | top: 70px;
11 | right: 50px;
12 | z-index: 5;
13 | background-color: #4694fc;
14 | box-shadow: 0px 2px 10px lightgray;
15 | cursor: pointer;
16 | display: flex;
17 | justify-content: center;
18 | align-items: center;
19 | font-size: 25px;
20 | animation: icon 0.7s ease-in-out;
21 | transition: 0.5s;
22 | color: white;
23 | }
24 |
25 | .cancel:hover {
26 | transform: scale(1.2);
27 | box-shadow: 0 10px 20px lightgray;
28 | }
29 |
30 |
31 | @keyframes icon {
32 | 0%{
33 | transform: scale(0.2);
34 |
35 | }
36 | 50%{
37 | transform: scale(1.5);
38 | }
39 | 100%{
40 | transform: scale(1);
41 | }
42 | }
43 |
44 | .add-product {
45 | background-color: white;
46 | color: gray;
47 | margin: 50px;
48 | padding: 20px 50px 50px 50px;
49 | box-shadow: 0px 2px 10px lightgray;
50 | display: flex;
51 | flex-direction: column;
52 | border-radius: 5px;
53 |
54 | h1 {
55 | color: #4694fc;
56 | margin-top: 0px;
57 | text-align: center;
58 | font-weight: 500;
59 | }
60 |
61 | .success {
62 | position: fixed;
63 | height: 100%;
64 | width: 100%;
65 | left: 0;
66 | top: 0;
67 |
68 | div {
69 | margin: 150px auto;
70 | height:80px;
71 | width: 250px;
72 | background-color: #4694fc;
73 | color: white;
74 | font-size: 40px;
75 | display: flex;
76 | justify-content: center;
77 | align-items: center;
78 | border-radius: 5px;
79 | animation: success 1s ease-in-out;
80 | }
81 | }
82 |
83 |
84 | @keyframes success {
85 | 0%{
86 | transform: scale(0.2);
87 |
88 | }
89 | 50%{
90 | transform: scale(1.3);
91 | }
92 | 100%{
93 | transform: scale(1);
94 | }
95 | }
96 |
97 | .photo {
98 | .image {
99 | display: flex;
100 | justify-content: center;
101 |
102 | img {
103 | width: 60%;
104 | height: 60%;
105 | }
106 | }
107 |
108 | label {
109 | margin-bottom: 2px;
110 | font-weight: 520;
111 | color: #4694fc;
112 | font-size: 17px;
113 | }
114 |
115 | input {
116 | margin-bottom: 15px;
117 | }
118 |
119 | }
120 |
121 | form {
122 | display: flex;
123 | flex-direction: column;
124 |
125 | label {
126 | margin-bottom: 2px;
127 | font-weight: 520;
128 | color: #4694fc;
129 | }
130 |
131 | input {
132 | margin-bottom: 15px;
133 |
134 | border: 1px solid lightblue;
135 | padding: 5px 8px;
136 | font-size: 16px;
137 | }
138 |
139 | select {
140 | margin-bottom: 10px;
141 | font-size: 16px;
142 | padding: 2px 5px;
143 | border: 1px solid lightblue;
144 | background-color: white;
145 | }
146 |
147 | .size-stock {
148 | display: grid;
149 | grid-template-columns: 1fr 1fr;
150 |
151 | input {
152 | border-radius: 5px;
153 | width: 50%;
154 | margin-bottom: 2px;
155 | }
156 |
157 | hr {
158 | margin-top: 1px;
159 | border: none;
160 | height: 2px;
161 | background-color: #4694fc;
162 | border-radius: 2px;
163 | opacity: 0.5;
164 | }
165 |
166 | .size {
167 | font-size: 20px;
168 | color: black;
169 | font-weight: 500;
170 | }
171 |
172 | .stock {
173 | font-size: 20px;
174 | color: #4694fc;
175 | font-weight: 500;
176 | }
177 |
178 | .stock span {
179 | padding: 3px 0;
180 | border-radius: 50%;
181 | margin-left: 10px;
182 | color: white;
183 | background-color: #ED515E;
184 | cursor: pointer;
185 | }
186 |
187 | }
188 |
189 | .description {
190 | height: 300px;
191 | margin-bottom: 100px;
192 | }
193 |
194 | .message {
195 | color: red;
196 | margin: 0 auto;
197 | margin-bottom: 5px;
198 | }
199 |
200 | .add {
201 | border: 1px solid #4694fc;
202 | background: #4694fc;
203 | text-align: center;
204 | color: white;
205 | padding: 3px 0;
206 | margin-bottom: 15px;
207 | border-radius: 20px;
208 | cursor: pointer;
209 | }
210 |
211 | .add:hover {
212 | background-color: white;
213 | color: #4694fc;
214 | }
215 |
216 | button {
217 | border: 1px solid #4694fc;
218 | background-color: #4694fc;
219 | color: white;
220 | font-size: 20px;
221 | font-weight: 520;
222 | cursor: pointer;
223 | padding: 5px 0;
224 | box-shadow: 0px 8px 20px lightgray;
225 |
226 | }
227 |
228 | button:hover {
229 | background-color: white;
230 | color: #4694fc;
231 | }
232 | }
233 | }
234 | }
235 |
236 | @media (max-width: 700px){
237 | .update-product {
238 | padding-left: 0px;
239 | padding-bottom: 50px;
240 |
241 | .cancel {
242 | right: 10px;
243 | }
244 |
245 | .add-product {
246 | margin: 15px;
247 | padding: 20px;
248 |
249 | .photo {
250 | .image {
251 | img {
252 | width: 100%;
253 | }
254 | }
255 | }
256 | }
257 |
258 | }
259 | }
--------------------------------------------------------------------------------
/src/components/Product/addproduct.scss:
--------------------------------------------------------------------------------
1 | .add-wrapper {
2 | padding-top: 60px;
3 | padding-left: 150px;
4 |
5 | .cancel {
6 | height: 50px;
7 | width: 50px;
8 | border-radius: 25px;
9 | position: fixed;
10 | top: 70px;
11 | right: 50px;
12 | z-index: 5;
13 | background-color: #4694fc;
14 | box-shadow: 0px 2px 10px lightgray;
15 | cursor: pointer;
16 | display: flex;
17 | justify-content: center;
18 | align-items: center;
19 | font-size: 25px;
20 | animation: icon 0.7s ease-in-out;
21 | transition: 0.5s;
22 | color: white;
23 | }
24 |
25 | .cancel:hover {
26 | transform: scale(1.2);
27 | box-shadow: 0 10px 20px lightgray;
28 | }
29 |
30 |
31 | @keyframes icon {
32 | 0%{
33 | transform: scale(0.2);
34 |
35 | }
36 | 50%{
37 | transform: scale(1.5);
38 | }
39 | 100%{
40 | transform: scale(1);
41 | }
42 | }
43 |
44 | .add-product {
45 | background-color: white;
46 | color: gray;
47 | margin: 50px;
48 | padding: 20px 50px 50px 50px;
49 | box-shadow: 0px 2px 10px lightgray;
50 | display: flex;
51 | flex-direction: column;
52 | border-radius: 5px;
53 |
54 | h1 {
55 | color: #4694fc;
56 | margin-top: 0px;
57 | text-align: center;
58 | font-weight: 500;
59 | }
60 |
61 | .success {
62 | position: fixed;
63 | height: 100%;
64 | width: 100%;
65 | left: 0;
66 | top: 0;
67 |
68 | div {
69 | margin: 150px auto;
70 | height:80px;
71 | width: 250px;
72 | background-color: #4694fc;
73 | color: white;
74 | font-size: 40px;
75 | display: flex;
76 | justify-content: center;
77 | align-items: center;
78 | border-radius: 5px;
79 | animation: success 1s ease-in-out;
80 | }
81 | }
82 |
83 |
84 | @keyframes success {
85 | 0%{
86 | transform: scale(0.2);
87 |
88 | }
89 | 50%{
90 | transform: scale(1.3);
91 | }
92 | 100%{
93 | transform: scale(1);
94 | }
95 | }
96 |
97 | .photo {
98 | .image {
99 | display: flex;
100 | justify-content: center;
101 |
102 | img {
103 | width: 60%;
104 | height: 60%;
105 | }
106 | }
107 |
108 | label {
109 | margin-bottom: 2px;
110 | font-weight: 520;
111 | color: #4694fc;
112 | font-size: 17px;
113 | }
114 |
115 | input {
116 | margin-bottom: 15px;
117 | }
118 |
119 | }
120 |
121 | form {
122 | display: flex;
123 | flex-direction: column;
124 |
125 | label {
126 | margin-bottom: 2px;
127 | font-weight: 520;
128 | color: #4694fc;
129 | }
130 |
131 | input {
132 | margin-bottom: 15px;
133 | border: 1px solid lightblue;
134 | padding: 5px 8px;
135 | font-size: 16px;
136 | }
137 |
138 | select {
139 | margin-bottom: 10px;
140 | font-size: 16px;
141 | padding: 2px 5px;
142 | border: 1px solid lightblue;
143 | background-color: white;
144 | }
145 |
146 | .size-stock {
147 | display: grid;
148 | grid-template-columns: 1fr 1fr;
149 |
150 | input {
151 | width: 50%;
152 | margin-bottom: 2px;
153 | }
154 |
155 | hr {
156 | margin-top: 1px;
157 | border: none;
158 | height: 2px;
159 | background-color: #4694fc;
160 | border-radius: 2px;
161 | opacity: 0.5;
162 | }
163 |
164 | .size {
165 | font-size: 20px;
166 | color: black;
167 | font-weight: 500;
168 | }
169 |
170 | .stock {
171 | font-size: 20px;
172 | color: #4694fc;
173 | font-weight: 500;
174 | position: relative;
175 |
176 | span {
177 | top: -6px;
178 | padding: 3px 0;
179 | border-radius: 50%;
180 | position: absolute;
181 | right: 0;
182 | color: white;
183 | background-color: #ED515E;
184 | cursor: pointer;
185 | }
186 | }
187 |
188 | }
189 |
190 | .description {
191 | height: 300px;
192 | margin-bottom: 100px;
193 | }
194 |
195 | .message {
196 | color: red;
197 | margin: 0 auto;
198 | margin-bottom: 5px;
199 | }
200 |
201 | .add {
202 | border: 1px solid #4694fc;
203 | background: #4694fc;
204 | text-align: center;
205 | color: white;
206 | padding: 3px 0;
207 | margin-bottom: 15px;
208 | border-radius: 20px;
209 | cursor: pointer;
210 |
211 | }
212 |
213 | .add:hover {
214 | background-color: white;
215 | color: #4694fc;
216 | }
217 |
218 | button {
219 | border: 1px solid #4694fc;
220 | background-color: #4694fc;
221 | color: white;
222 | font-size: 20px;
223 | font-weight: 520;
224 | cursor: pointer;
225 | padding: 5px 2px;
226 | box-shadow: 0px 2px 10px lightgray;
227 | transition: 0.5s;
228 | }
229 |
230 | button:hover {
231 | background-color: white;
232 | color: #4694fc;
233 |
234 | }
235 | }
236 | }
237 | }
238 |
239 | @media (max-width: 700px){
240 | .add-wrapper {
241 | padding-left: 0px;
242 | padding-bottom: 50px;
243 |
244 | .cancel {
245 | right: 10px;
246 | }
247 |
248 | .add-product {
249 | margin: 15px;
250 | padding: 15px;
251 |
252 | .photo {
253 | .image {
254 | img {
255 | width: 100%;
256 | }
257 | }
258 | }
259 | }
260 | }
261 | }
--------------------------------------------------------------------------------
/src/components/Orders/new.scss:
--------------------------------------------------------------------------------
1 | .new-order {
2 | position: relative;
3 |
4 | .load {
5 | div {
6 | width: 100%;
7 | height: 50px;
8 | background-color: lightgray;
9 | margin-bottom: 10px;
10 | animation: load 2s infinite;
11 | }
12 |
13 | @keyframes laod {
14 | 0%{
15 | opacity: 0.2;
16 | }
17 | 50%{
18 | opacity: 0.9;
19 | }
20 | 100%{
21 | opacity: 0.2;
22 | }
23 | }
24 |
25 | }
26 |
27 | .confirm {
28 | position: absolute;
29 | background-color: #4694FC;
30 | color: white;
31 | padding: 10px 20px;
32 | right: 10px;
33 | font-size: 16px;
34 | cursor: pointer;
35 | border-radius: 2px;
36 | font-weight: 500;
37 | }
38 |
39 | .confirm:hover {
40 | box-shadow: 0 10px 20px lightgray;
41 | }
42 |
43 | .confirm-process {
44 | background-color: rgba($color: #000000, $alpha: 0.5);
45 | position: fixed;
46 | height: 100%;
47 | width: 100%;
48 | top: 0;
49 | left: 0;
50 | z-index: 250;
51 |
52 | div {
53 | background-color: white;
54 | width: 300px;
55 | padding: 20px;
56 | margin: 100px auto;
57 | border-radius: 10px;
58 |
59 | label {
60 | font-size: 18px;
61 | font-weight: 500;
62 | }
63 |
64 | input {
65 | border: 1px solid #4694FC;
66 | border-radius: 3px;
67 | width: 95%;
68 | margin-top: 5px;
69 | margin-bottom: 10px;
70 | font-size: 18px;
71 | padding: 3px 5px;
72 | }
73 |
74 | button {
75 | margin-left: 35px;
76 | background-color: #4694FC;
77 | border: none;
78 | font-size: 16px;
79 | padding: 5px 20px;
80 | border-radius: 25px;
81 | color: white;
82 | cursor: pointer;
83 | }
84 |
85 | button:hover {
86 | box-shadow: 0 10px 10px lightgrey;
87 | }
88 | }
89 | }
90 |
91 | .order {
92 | margin-bottom: 10px;
93 | border-radius: 5px;
94 | cursor: pointer;
95 | background-color: white;
96 | padding: 10px;
97 | box-shadow: 0 3px 5px lightgray;
98 | animation: order 1s ease-in-out;
99 |
100 | span {
101 | color: #505050;
102 | font-size: 18px;
103 | font-weight: 500;
104 | }
105 | }
106 |
107 | .hide {
108 | display: grid;
109 | grid-template-columns: 1fr 1fr 1fr 1fr;
110 |
111 | span {
112 | font-size: 16px;
113 | }
114 | }
115 |
116 | @keyframes order {
117 | 0%{
118 | margin-top: -50px;
119 | opacity: 0;
120 | }
121 | 100%{
122 | margin-top: 0;
123 | opacity: 1;
124 | }
125 | }
126 |
127 | .shipping {
128 | font-size: 16px;
129 | display: grid;
130 | grid-gap: 10px;
131 | grid-template-columns: 1fr 1fr;
132 | animation: shipping 1s ease-in-out;
133 |
134 | .detail div {
135 | color: #4694FC;
136 | display: grid;
137 | grid-template-columns: 1fr 2fr;
138 | grid-gap: 10px;
139 | padding: 10px;
140 | border-bottom: 1px solid rgb(195, 218, 247);
141 |
142 | span {
143 | font-size: 16px;
144 | display: grid;
145 | color: #505050;
146 | }
147 | }
148 |
149 | .payment div {
150 | color: #4694FC;
151 | display: grid;
152 | grid-template-columns: 1fr 2fr;
153 | grid-gap: 10px;
154 | padding: 10px;
155 | border-bottom: 1px solid rgb(195, 218, 247);
156 |
157 | span {
158 | font-size: 16px;
159 | display: grid;
160 | color: #505050;
161 | }
162 | }
163 |
164 |
165 | }
166 |
167 | .product-shipping {
168 | margin-top: 10px;
169 | span {
170 | color: #505050;
171 | font-size: 18px;
172 | font-weight: 500;
173 | }
174 | .product {
175 | color: #505050;
176 | font-size: 16px;
177 | margin-top: 5px;
178 | border-radius: 2px;
179 | background-color: #F7F8F9;
180 | display: grid;
181 | grid-template-columns: 1fr 1fr 1fr;
182 | padding: 3px 5px;
183 | box-shadow: 0 3px 5px lightgray;
184 |
185 | }
186 | }
187 | }
188 |
189 | @media (max-width: 700px){
190 | .new-order {
191 | position: relative;
192 | .confirm {
193 | position: absolute;
194 | background-color: #4694FC;
195 | color: white;
196 | padding: 10px 20px;
197 | right: 10px;
198 | font-size: 16px;
199 | cursor: pointer;
200 | border-radius: 2px;
201 | font-weight: 500;
202 | }
203 |
204 | .confirm:hover {
205 | box-shadow: 0 10px 20px lightgray;
206 | }
207 |
208 | .order {
209 | background-color: white;
210 | padding: 10px;
211 |
212 | span {
213 | color: #505050;
214 | font-size: 18px;
215 | font-weight: 500;
216 | }
217 | }
218 |
219 | .hide {
220 | display: grid;
221 | grid-template-columns: 1fr;
222 | }
223 |
224 | .shipping {
225 | margin-top: 10px;
226 | font-size: 16px;
227 | display: grid;
228 | grid-gap: 10px;
229 | grid-template-columns: 1fr;
230 |
231 | .detail div {
232 | color: #4694FC;
233 |
234 | display: grid;
235 | grid-template-columns: 1fr 2fr;
236 | grid-gap: 10px;
237 | padding: 10px;
238 | border-bottom: 1px solid rgb(195, 218, 247);
239 |
240 | span {
241 | font-size: 18px;
242 | display: grid;
243 | color: #505050;
244 | }
245 | }
246 |
247 | .payment div {
248 | color: #4694FC;
249 |
250 | display: grid;
251 | grid-template-columns: 1fr 2fr;
252 | grid-gap: 10px;
253 | padding: 10px;
254 | border-bottom: 1px solid rgb(195, 218, 247);
255 |
256 | span {
257 | font-size: 18px;
258 | display: grid;
259 | color: #505050;
260 | }
261 | }
262 | }
263 |
264 | .product-shipping {
265 | margin-top: 10px;
266 | span {
267 | color: #505050;
268 | font-size: 18px;
269 | font-weight: 500;
270 | }
271 | .product {
272 | grid-template-columns: 1fr;
273 | }
274 | }
275 | }
276 | }
--------------------------------------------------------------------------------
/src/components/Categories/Category.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import axios from 'axios'
3 | import { url, headers } from '../../config'
4 | import _ from 'lodash'
5 | import { connect } from 'react-redux'
6 | import Loading from '../Loading'
7 |
8 | import SubCategory from './SubCategory'
9 |
10 | class Category extends Component {
11 | state = {
12 | open_update : true,
13 | open_add : true,
14 | comfirm_delete : true,
15 | name : '',
16 | message : '',
17 | sub_categories : [],
18 | loading: false
19 | }
20 |
21 | componentDidMount() {
22 | let sub_categories = this.props.category.subcategories
23 | if(sub_categories){
24 | this.setState({ sub_categories })
25 | }
26 | this.fetchSubCategories()
27 | }
28 |
29 | fetchSubCategories = () => {
30 | axios.get( url + '/subcategory/category/' + this.props.category.category_id )
31 | .then(res=>{
32 | if( _.isArray(res.data.datasub) )
33 | this.setState({ sub_categories: res.data.datasub })
34 | else
35 | this.setState({ sub_categories: [] })
36 | })
37 | }
38 |
39 | deleteCategory(id) {
40 | axios.delete( url+ '/category/' + id , headers(this.props.user.token) )
41 | .then(res=>{
42 | this.props.update()
43 | })
44 | }
45 |
46 |
47 | openUpdateCategory = (name) =>{
48 | this.setState({ name, open_update: !this.state.open_update, message: '' })
49 | }
50 |
51 | updateCategory(e, id) {
52 | e.preventDefault()
53 | const data = {
54 | name: this.state.name,
55 | }
56 |
57 | axios.put( url + '/category/' + id , data ,headers(this.props.user.token) )
58 | .then(res=>{
59 | this.setState({ message: 'Success update category', open_update: true })
60 | this.props.update()
61 | })
62 | .catch(err=>{ this.setState({ message: 'Failed update category' }) })
63 | }
64 |
65 | openAddSubCategory = () =>{
66 | this.setState({ open_add: !this.state.open_add, message: '' })
67 |
68 | }
69 |
70 | handleImage = (e) => {
71 | let file = e.target.files[0]
72 | let reader = new FileReader()
73 |
74 | reader.readAsDataURL(file)
75 |
76 | reader.onloadend = () => {
77 | this.setState({
78 | file: file,
79 | image: reader.result
80 | })
81 | }
82 | }
83 |
84 | addSubCategory(e, id) {
85 | e.preventDefault()
86 | const { name, file } = this.state
87 | this.setState({ loading: true })
88 | let formData = new FormData()
89 | formData.append('image', file)
90 |
91 | axios.post( url + "/subcategory/upload-image", formData )
92 | .then(res=>{
93 | const data = {
94 | name : name,
95 | image : res.data.url,
96 | category_id : id
97 | }
98 | axios.post( url + '/subcategory', data , headers(this.props.user.token) )
99 | .then(res=>{
100 |
101 | this.fetchSubCategories()
102 | this.setState({ message: 'Success add subcategory', open_add: true, name: '', image: null, loading: false })
103 | })
104 | .catch(err=>{
105 | this.setState({ message: 'Failed add subcategory', loading: false })
106 | })
107 | })
108 | .catch(err=>{
109 | this.setState({ message: 'Failed upload image', loading: false})
110 | })
111 | }
112 |
113 |
114 | render() {
115 | const { loading, image, open_update, name, open_add, comfirm_delete, message, sub_categories } = this.state
116 | const { category } = this.props
117 | return (
118 |
119 | { loading &&
}
120 |
121 |
{message}
122 |
123 |
124 | {category.category_name}
125 |
126 |
127 |
{this.setState({ comfirm_delete : false})}} className="delete">
128 |
129 |
130 |
{this.openUpdateCategory(category.category_name)}} className="update">
131 |
132 |
133 |
134 | {
135 | open_add ? :
136 | }
137 |
138 |
139 |
140 |
141 | { //open comfirm delete category
142 | comfirm_delete ? '' :
143 |
144 |
145 |
Are you sure want to delete?
146 |
147 |
148 |
149 |
150 | }
151 |
152 |
153 | { //open input for update category
154 | open_update ? '' :
155 |
156 |
160 |
161 | }
162 |
163 | { image ?

: '' }
164 |
165 |
166 | { //open input for add subcategory
167 | open_add ? '' :
168 |
169 |
174 |
175 | }
176 |
177 | { //list sub categories
178 | sub_categories.map(sub_category=>{
179 | return(
180 |
181 | )
182 | })
183 | }
184 |
185 |
186 |
187 |
188 | );
189 | }
190 | }
191 |
192 | const mapStateToProp = (state) => {
193 | return({
194 | user: state.userReducer
195 | })
196 | }
197 |
198 | export default connect(mapStateToProp)(Category);
--------------------------------------------------------------------------------
/src/components/Categories/categories.scss:
--------------------------------------------------------------------------------
1 | .categories {
2 | padding-top: 50px;
3 | padding-left: 150px;
4 |
5 | .wrapper {
6 | padding: 50px;
7 | color: #4694fc;
8 |
9 | .message {
10 | color: #ff5555;
11 | }
12 |
13 | .loading-list {
14 | background-color: white;
15 | margin-bottom: 15px;
16 | height: 50px;
17 | animation: loading-list 2s infinite;
18 | }
19 |
20 | @keyframes loading-list {
21 | 0%{
22 | opacity: 0.2;
23 | }
24 | 50%{
25 | opacity: 0.6;
26 | }
27 | 100%{
28 | opacity: 0.2;
29 | }
30 | }
31 |
32 | .new {
33 | height: 50px;
34 | width: 50px;
35 | border-radius: 25px;
36 | position: absolute;
37 | top: 70px;
38 | right: 50px;
39 | z-index: 5;
40 | background-color: #4694fc;
41 | box-shadow: 0px 2px 10px lightgray;
42 | cursor: pointer;
43 | display: flex;
44 | justify-content: center;
45 | align-items: center;
46 | font-size: 25px;
47 | animation: icon 0.7s ease-in-out;
48 | transition: 0.5s;
49 | }
50 |
51 | .new:hover {
52 | transform: scale(1.2);
53 | box-shadow: 0 10px 20px lightgray;
54 | }
55 |
56 |
57 | @keyframes icon {
58 | 0%{
59 | transform: scale(0.2);
60 |
61 | }
62 | 50%{
63 | transform: scale(1.5);
64 | }
65 | 100%{
66 | transform: scale(1);
67 | }
68 | }
69 |
70 | i {
71 | color: white;
72 | }
73 |
74 | .comfirm-delete {
75 | position: fixed;
76 | color: white;
77 | width: 100%;
78 | height: 100%;
79 | z-index: 300;
80 | top: 0;
81 | left: 0;
82 | background-color: rgba($color: #000000, $alpha: 0.5);
83 |
84 |
85 | div {
86 | margin: 80px auto;
87 | opacity: 1;
88 | width: 200px;
89 | background-color: #4694fc;
90 | padding: 20px;
91 | border-radius: 10px;
92 | text-align: center;
93 | animation: comfirm-delete 1s ease-in-out;
94 |
95 | h3 {
96 | margin: 0;
97 | font-weight: 500;
98 | }
99 |
100 | button {
101 | padding: 2px 20px;
102 | border-radius: 2px;
103 | border: 1px solid white;
104 | margin-right: 10px;
105 | margin-left: 10px;
106 | margin-top: 40px;
107 | background-color: #4694fc;
108 | color: white;
109 | font-size: 15px;
110 | cursor: pointer;
111 | }
112 |
113 | button:hover {
114 | background: white;
115 | color: #4694fc;
116 | }
117 |
118 | }
119 |
120 | @keyframes comfirm-delete {
121 | 0%{
122 | margin-top: -100px;
123 | opacity: 0;
124 | }
125 | 50%{
126 | margin-top: 110px;
127 | }
128 | 100%{
129 | opacity: 1;
130 | margin-top: 80px;
131 | }
132 | }
133 | }
134 |
135 | .category {
136 | margin: 10px 0;
137 | display: flex;
138 | justify-content: space-between;
139 | border-bottom: 2px solid #b5d3fa;
140 | padding: 3px;
141 | color: #505050;
142 | align-items: center;
143 |
144 |
145 | .actions {
146 | display: flex;
147 | }
148 |
149 | span {
150 | margin-left: 3px;
151 | font-size: 20px;
152 | font-weight: 500;
153 | }
154 |
155 | .add {
156 | height: 30px;
157 | width: 30px;
158 | border-radius: 20px;
159 | background-color: #4694fc;
160 | margin-left: 7px;
161 | box-shadow: 0px 2px 10px lightgray;
162 | display: flex;
163 | justify-content: center;
164 | align-items: center;
165 | font-size: 15px;
166 | cursor: pointer;
167 | }
168 |
169 | .update {
170 | height: 30px;
171 | width: 30px;
172 | border-radius: 20px;
173 | background-color: #4DB649;
174 | margin-left: 7px;
175 | box-shadow: 0px 2px 10px lightgray;
176 | display: flex;
177 | justify-content: center;
178 | align-items: center;
179 | font-size: 15px;
180 | cursor: pointer;
181 | }
182 |
183 | .delete {
184 | height: 30px;
185 | width: 30px;
186 | border-radius: 20px;
187 | background-color: #ff5555;
188 | box-shadow: 0px 2px 10px lightgray;
189 | display: flex;
190 | justify-content: center;
191 | align-items: center;
192 | font-size: 15px;
193 | cursor: pointer;
194 | }
195 |
196 | }
197 |
198 | .add-subcategory {
199 | margin-top: 10px;
200 | background-color: white;
201 | padding: 5px;
202 | box-shadow: 0 3px 10px lightgray;
203 | animation: add 1s ease-in-out;
204 | border-radius: 3px;
205 | width: 98%;
206 | margin-bottom: 10px;
207 |
208 | @keyframes add {
209 | 0%{
210 | opacity: 0;
211 | width: 0%;
212 | }
213 | 50%{
214 | width: 105%;
215 | }
216 | 100%{
217 | width: 99%;
218 | opacity: 1;
219 | }
220 | }
221 |
222 | form {
223 | display: flex;
224 | justify-content: space-between;
225 | }
226 |
227 | input {
228 | font-size: 14px;
229 | padding: 5px;
230 | border: 1px solid #87B7FB;
231 | background-color: #f5f5f5;
232 | border-radius: 3px;
233 | width: 92%;
234 | margin-right: 10px;
235 | }
236 |
237 | button {
238 | background-color: #4694fc;
239 | border: none;
240 | color: white;
241 | border-radius: 3px;
242 | padding: 0 20px;
243 | cursor: pointer;
244 | }
245 | }
246 |
247 |
248 | .list-sub {
249 | display: grid;
250 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
251 | grid-gap: 10px;
252 | }
253 |
254 | }
255 | }
256 |
257 | // responsive mobile
258 | @media (max-width: 700px){
259 | .categories {
260 | padding-left: 0px;
261 | padding-bottom: 60px;
262 |
263 | .wrapper {
264 | padding: 30px 10px;
265 |
266 | .new {
267 | height: 40px;
268 | width: 40px;
269 | top: 65px;
270 | right: 10px;
271 | }
272 |
273 | .list-sub {
274 | display: grid;
275 | grid-template-columns: 1fr 1fr;
276 | grid-gap: 10px;
277 | }
278 |
279 | img {
280 | width: 100%;
281 | }
282 |
283 | .add-subcategory {
284 | form {
285 | display: flex;
286 | flex-direction: column;
287 | input {
288 | margin-bottom: 5px;
289 | width: auto;
290 | }
291 |
292 | button {
293 | padding: 5px 0;
294 | }
295 | }
296 | }
297 | }
298 |
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/src/components/Admin/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './admin.scss'
3 | import { connect } from 'react-redux'
4 | import axios from 'axios'
5 | import { url, headers } from '../../config'
6 | import { userAction } from '../../actions'
7 | import Header from '../Header'
8 | import Navbar from '../Navbar'
9 | import Loading from '../Loading'
10 |
11 |
12 | class index extends Component {
13 | state={
14 | edit: false,
15 | username: '',
16 | photo: '',
17 | name: '',
18 | email: '',
19 | image: '',
20 | file: null,
21 | message: '',
22 | loading: false,
23 | change: false,
24 | current_password: '',
25 | new_password: ''
26 | }
27 |
28 | handleChange = (e) => {
29 | this.setState({
30 | [e.target.name] : e.target.value
31 | })
32 | }
33 |
34 | componentDidMount(){
35 | let data = this.props.user.data
36 | if(data){
37 | const { username, email, photo, name } = data
38 | this.setState({ username, email, photo, name })
39 | }
40 | }
41 |
42 | handleImage = (e) => {
43 | let file = e.target.files[0]
44 | let reader = new FileReader()
45 |
46 | reader.readAsDataURL(file)
47 |
48 | reader.onloadend = () => {
49 | this.setState({
50 | file: file,
51 | image: reader.result
52 | })
53 | }
54 | }
55 |
56 | saveProfile = (e) => {
57 | e.preventDefault()
58 | this.setState({ loading: true })
59 | let token = this.props.user.token
60 | const { username, name, email, photo, file } = this.state
61 | if(file && username && name && email){
62 | let formData = new FormData()
63 | formData.append('image' , file)
64 | axios.post( url + "/admin/profile/upload-image", formData ,headers(token) )
65 | .then( res => {
66 | let data = { username, name, email, photo: res.data.url }
67 | axios.put( url + "/admin/profile" , data, headers(token) )
68 | .then( res=>{
69 | if(res.data.success){
70 | this.props.userAction(res.data.profile, true, token)
71 | const { name, email, username, photo } = res.data.profile
72 | this.setState({ message: "Success update profile", loading: false, edit: false, name, email, username, photo })
73 | }
74 | })
75 | .catch(err=>{
76 | this.setState({ message: "Failed update profile", loading: false, edit: false })
77 | })
78 | })
79 | .catch(err=>{
80 | this.setState({ message: "Failed Update", loading: false, edit: false })
81 | })
82 | }
83 |
84 | if( username && name && email ) {
85 | let data = { username, name, email, photo }
86 | axios.put( url + "/admin/profile" , data, headers(token) )
87 | .then( res=>{
88 | console.log(res)
89 | if(res.data.success){
90 | this.props.userAction(res.data.profile, true, token)
91 | this.setState({ message: "Success update profile", loading: false, edit: false })
92 | }
93 |
94 | })
95 | .catch(err=>{
96 | this.setState({ message: "Failed update profile", loading: false, edit: false })
97 | })
98 | }
99 | }
100 |
101 | changePassword = (e) => {
102 | e.preventDefault()
103 | const { new_password, current_password } = this.state
104 | let token = this.props.user.token
105 | if(new_password && current_password){
106 | axios.put( url + "/admin/profile/change-password" , { current_password, new_password } , headers(token) )
107 | .then(res => {
108 | this.setState({ change: false, message: 'Success change password' })
109 | })
110 | .catch(err => {
111 | this.setState({ change: false, message: 'Failed change password' })
112 | })
113 | } else {
114 | this.setState({ change: false, message: 'Failed change password' })
115 | }
116 | }
117 |
118 | render() {
119 | const { change, username, email, photo, name , edit, image, loading, message } = this.state
120 | console.log(this.state)
121 | return (
122 |
123 |
124 |
125 |
126 | { loading ?
: '' }
127 |
128 | { !change ? '' :
129 |
142 | }
143 |
144 | {
145 | edit ?
146 |
147 | { message ?
{message} : '' }
148 |
this.setState({ edit: false })} className="edit">
149 |
150 |
151 |
152 |

153 |
154 |
155 |
156 | Username
157 |
158 |
159 | Name
160 |
161 |
162 | Email
163 |
164 |
165 |
166 |
167 |
168 | :
169 |
170 | { message ?
{message} : '' }
171 |
this.setState({ edit: true })} className="edit">
172 |
173 |
174 |
175 |

176 |
177 |
178 | Username{username}
179 |
180 |
181 | Name{name}
182 |
183 |
184 | Email{email}
185 |
186 |
187 | Password
188 |
189 |
190 | }
191 |
192 |
193 |
194 | );
195 | }
196 | }
197 |
198 | const mapStateToProps = (state) => {
199 | return {
200 | user: state.userReducer
201 | }
202 | }
203 |
204 | export default connect(mapStateToProps,{ userAction })(index);
--------------------------------------------------------------------------------
/src/components/Products/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './products.scss'
3 | import axios from 'axios'
4 | import { url } from '../../config'
5 | import { connect } from 'react-redux'
6 | import { categoryAction } from '../../actions'
7 | import { Link } from 'react-router-dom'
8 | import _ from 'lodash'
9 |
10 | import Product from './Product'
11 | import Header from '../Header'
12 | import Navbar from '../Navbar'
13 |
14 | class index extends Component {
15 | state = {
16 | show: 'card',
17 | loading : true,
18 | products : [1,2,3,4,5,6,7,8,9,10],
19 | categories : [],
20 | subcategories : [],
21 | category_id : 0,
22 | sub_category_id: 0,
23 | top : '100px',
24 | search : ''
25 | }
26 |
27 |
28 | componentDidMount(){
29 | this.fetchProducts()
30 | let category = localStorage.getItem('categories')
31 | if(_.isString(category)){
32 | let categories = JSON.parse(category)
33 | if(_.isArray(categories))
34 | this.setState({ categories, loading: false })
35 | }
36 | this.fetchCategories()
37 |
38 | window.addEventListener('scroll', ()=>{
39 | let scroll = window.scrollY
40 | if( scroll>0 ) {
41 | this.setState({ top: '65px' })
42 | } else {
43 | this.setState({ top: '100px' })
44 | }
45 | })
46 | }
47 |
48 | fetchCategories = () => {
49 | axios.get( url + '/category' )
50 | .then(res=>{
51 | if(_.isArray(res.data.data)){
52 | this.props.categoryAction(res.data.data)
53 | this.setState({ categories: res.data.data, loading: false })
54 | }
55 | })
56 | }
57 |
58 |
59 |
60 | fetchProducts = () => {
61 | let products = JSON.parse(localStorage.getItem('products'))
62 | if(_.isArray(products)){
63 | this.setState({ products, loading: false })
64 | }
65 | axios.get( url + "/product" )
66 | .then(res=>{
67 | if( _.isArray(res.data) )
68 | this.setState({ products: res.data, loading: false })
69 | localStorage.setItem('products', JSON.stringify(res.data))
70 | })
71 | }
72 |
73 | selectCategory = (e) => {
74 | let id = e.target.value
75 |
76 | this.setState({ category_id: id, sub_category_id: 0 })
77 | if(id <= 0){
78 | this.fetchProducts()
79 | } else {
80 | this.fetchProductsCategory(id)
81 | }
82 |
83 | let categories = this.state.categories
84 | let selectcategories = categories.filter((el)=>{
85 | return(
86 | el.category_id == id
87 | )
88 | })
89 |
90 | if(!_.isUndefined(selectcategories[0])){
91 | this.setState({ subcategories: selectcategories[0].subcategories })
92 | } else {
93 | this.setState({ subcategories: [] })
94 | }
95 | }
96 |
97 | fetchProductsCategory(id){
98 | let products = JSON.parse(localStorage.getItem(`category${id}`))
99 | if(_.isArray(products)){
100 | this.setState({ products, loading: false })
101 | }
102 |
103 | axios.get( url + "/product/category/" + id )
104 | .then(res=>{
105 | if(_.isArray(res.data)){
106 | localStorage.setItem(`category${id}`, JSON.stringify(res.data))
107 | this.setState({ products: res.data, loading: false })
108 | }
109 | })
110 | }
111 |
112 | selectSubcategory = (e) => {
113 | let id = e.target.value
114 | let category = this.state.category_id
115 | this.setState({ sub_category_id: id })
116 | if(id <= 0){
117 | this.fetchProductsCategory(category)
118 | } else {
119 | let products = JSON.parse(localStorage.getItem(`category${category}${id}`))
120 | if(_.isArray(products)){
121 | this.setState({ products, loading: false })
122 | }
123 |
124 | axios.get( url + "/product/category/" + category + "?idSub=" + id )
125 | .then( res => {
126 | if(_.isArray(res.data)){
127 | localStorage.setItem(`category${category}${id}`, JSON.stringify(res.data))
128 | this.setState({ products: res.data, loading: false })
129 | }
130 | } )
131 | }
132 | }
133 |
134 | searchProduct = (e) => {
135 | e.preventDefault()
136 | axios.get( url + "/product?search=" + this.state.search )
137 | .then(res =>{
138 | if(_.isArray(res.data)){
139 | this.setState({ products: res.data })
140 | }
141 | })
142 | }
143 |
144 | render() {
145 | const { top, loading, products, show, categories, subcategories, sub_category_id, category_id, message } = this.state
146 | return (
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
Products
160 |
161 |
162 |
this.setState({ show: 'card' })}>
163 | cards
164 |
165 |
this.setState({ show: 'table' })} >
166 | table
167 |
168 |
169 |
170 |
171 |
172 |
173 |
179 |
180 |
181 |
182 |
183 |
189 |
190 |
191 |
192 |
196 |
197 |
198 |
199 | { // loading product
200 | loading ?
:
201 |
202 | { //list all products
203 | products.length === 0 ?
Empty :
204 | products.map(product=>
)
205 | }
206 |
207 | }
208 |
209 |
210 |
211 | );
212 | }
213 | }
214 |
215 | const mapStateToProps = (state) => {
216 | return({
217 | categories : state.categoriesReducer,
218 | user : state.userReducer
219 | })
220 | }
221 |
222 | export default connect(mapStateToProps , { categoryAction })(index);
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
98 |
--------------------------------------------------------------------------------
/src/components/Product/product.scss:
--------------------------------------------------------------------------------
1 | .detail-product {
2 | padding-top: 60px;
3 | padding-left: 150px;
4 |
5 | .comfirm-delete {
6 | position: fixed;
7 | color: white;
8 | width: 100%;
9 | height: 100%;
10 | z-index: 200;
11 | top: 0;
12 | left: 0;
13 | background-color: rgba($color: #000000, $alpha: 0.5);
14 |
15 | div {
16 | opacity: 1;
17 | margin: 80px auto;
18 | opacity: 1;
19 | width: 200px;
20 | background-color: #4694fc;
21 | padding: 20px;
22 | border-radius: 10px;
23 | text-align: center;
24 | animation: comfirm-delete 1s ease-in-out;
25 |
26 | h3 {
27 | margin: 0;
28 | font-weight: 500;
29 | }
30 |
31 | button {
32 | padding: 2px 20px;
33 | border-radius: 2px;
34 | border: 1px solid white;
35 | margin-right: 10px;
36 | margin-left: 10px;
37 | margin-top: 40px;
38 | background-color: #4694fc;
39 | color: white;
40 | font-size: 15px;
41 | cursor: pointer;
42 | }
43 |
44 | button:hover {
45 | background: white;
46 | color: #4694fc;
47 | }
48 |
49 | }
50 |
51 | @keyframes comfirm-delete {
52 | 0%{
53 | margin-top: -100px;
54 | opacity: 0;
55 | }
56 | 50%{
57 | margin-top: 110px;
58 | }
59 | 100%{
60 | opacity: 1;
61 | margin-top: 80px;
62 | }
63 | }
64 | }
65 |
66 | .top {
67 | background-color: white;
68 | display: flex;
69 | top: 0px;
70 | position: sticky;
71 | z-index: 100;
72 | padding: 0 20px;
73 | height: 60px;
74 | width: auto;
75 | align-items: center;
76 | justify-content: space-between;
77 | box-shadow: 0 2px 2px lightgray;
78 |
79 | .back {
80 | color: #4694fc;
81 | }
82 |
83 |
84 | .action {
85 | display: grid;
86 | grid-template-columns: 1fr 1fr;
87 | grid-gap: 8px;
88 | }
89 |
90 | .update {
91 | height: 25px;
92 | color: white;
93 | border-radius: 20px;
94 | background-color: #4DB649;
95 | display: flex;
96 | justify-content: center;
97 | align-items: center;
98 | font-size: 18px;
99 | cursor: pointer;
100 | height: 40px;
101 | width: 40px;
102 | transition: 0.5s;
103 | box-shadow: 0 5px 10px lightgray;
104 | }
105 |
106 | .delete {
107 | height: 25px;
108 | color: white;
109 | border-radius: 20px;
110 | background-color: #ff5555;
111 | display: flex;
112 | justify-content: center;
113 | align-items: center;
114 | font-size: 18px;
115 | cursor: pointer;
116 | height: 40px;
117 | width: 40px;
118 | transition: 0.5s;
119 | box-shadow: 0 5px 10px lightgray;
120 | }
121 |
122 | .delete:hover {
123 | transform: scale(1.2);
124 | box-shadow: 0 10px 20px lightgray;
125 | }
126 |
127 | .update:hover {
128 | transform: scale(1.2);
129 | box-shadow: 0 10px 20px lightgray;
130 | }
131 |
132 | }
133 |
134 |
135 | .wrapper {
136 | padding: 30px 50px 0px 50px;
137 | color: #4694fc;
138 | display: grid;
139 | grid-template-columns: 1fr 1fr;
140 | grid-gap: 50px;
141 | font-weight: 500;
142 |
143 |
144 | .back {
145 | color: #4694fc;
146 | position: absolute;
147 | top: 100px;
148 | left: 200px;
149 | }
150 |
151 | .image {
152 | img {
153 | width: 100%;
154 | max-height: 500px;
155 | }
156 |
157 | .load {
158 | background-color: lightgray;
159 | height: 450px;
160 | width: 100%;
161 | animation: load 2s ease-in-out infinite;
162 | }
163 |
164 | @keyframes load {
165 | 0%{
166 | opacity: 0.2;
167 | }
168 | 50%{
169 | opacity: 0.8;
170 | }
171 | 100%{
172 | opacity: 0.2;
173 | }
174 | }
175 |
176 |
177 | }
178 |
179 | .detail {
180 |
181 | .box {
182 | box-shadow: 0px 3px 3px lightgray;
183 | background-color: white;
184 | padding: 20px;
185 | border-radius: 3px;
186 | }
187 |
188 | .message {
189 | color: red;
190 | }
191 |
192 |
193 | .name {
194 | border-bottom: 1px solid lightgray;
195 | color: #505050;
196 | font-size: 22px;
197 | font-weight: 500;
198 | padding-bottom: 10px;
199 | }
200 |
201 | .code {
202 | padding: 10px 0;
203 | display: flex;
204 | align-items: center;
205 | justify-content: space-between;
206 | border-bottom: 1px solid lightgray;
207 | margin-top: 5px;
208 | span {
209 | color: #505050;
210 | font-size: 18px;
211 | font-weight: 500;
212 | }
213 | }
214 |
215 |
216 | .weight {
217 | padding: 10px 0;
218 | display: flex;
219 | align-items: center;
220 | justify-content: space-between;
221 | margin-top: 5px;
222 | border-bottom: 1px solid lightgray;
223 | span {
224 | color: #505050;
225 | font-size: 18px;
226 | font-weight: 500;
227 | }
228 | }
229 |
230 | .category div{
231 | padding: 10px 0;
232 | align-items: center;
233 | display: flex;
234 | justify-content: space-between;
235 | border-bottom: 1px solid lightgray;
236 | margin-top: 5px;
237 | span {
238 | color: #505050;
239 | font-size: 18px;
240 | font-weight: 500;
241 | }
242 | }
243 |
244 | .size-stock {
245 | margin-top: 5px;
246 | display: grid;
247 | padding: 10px 0;
248 | grid-template-columns: 1fr 1fr;
249 |
250 | hr {
251 | border: none;
252 | height: 1px;
253 | background-color: #4694fc;
254 | opacity: 0.5;
255 | margin-top: 0;
256 | }
257 |
258 | .stock {
259 | color: #505050;
260 | font-weight: 500;
261 | }
262 |
263 | }
264 |
265 |
266 | .price {
267 | display: flex;
268 | justify-content: space-between;
269 | align-items: center;
270 | box-shadow: 0px 3px 3px lightgray;
271 | border-radius: 3px;
272 | background-color: white;
273 | padding: 10px 20px;
274 | margin-top: 10px;
275 | span {
276 | color: #505050;
277 | font-size: 23px;
278 | font-weight: 500;
279 | }
280 | }
281 | }
282 | }
283 |
284 |
285 | .description {
286 | box-shadow: 0px 3px 3px lightgray;
287 | border-radius: 3px;
288 | background-color: white;
289 | margin-top: 5px;
290 | margin-left: 50px;
291 | margin-right: 50px;
292 | margin-bottom: 50px;
293 | padding: 20px;
294 | color: #4694fc;
295 | font-weight: 500;
296 | font-size: 20px;
297 |
298 | div {
299 | color: #505050;
300 | font-size: 16px;
301 | font-weight: 400;
302 |
303 | p {
304 | margin-top: 0px;
305 | margin-bottom: 0px;
306 | }
307 | }
308 | }
309 | }
310 |
311 | @media (max-width: 700px){
312 | .detail-product {
313 | padding-top: 60px;
314 | padding-left: 0px;
315 | padding-bottom: 10px;
316 |
317 | .top {
318 | padding: 0 10px;
319 | }
320 |
321 | .wrapper {
322 | padding: 10px;
323 | display: grid;
324 | grid-template-columns: 1fr;
325 | grid-gap: 10px;
326 |
327 |
328 | .detail {
329 | .update {
330 | right: 105px;
331 | }
332 |
333 | .delete {
334 | right: 10px;
335 | }
336 | }
337 | }
338 |
339 | .description {
340 | margin: 0px 10px 50px 10px;
341 | }
342 | }
343 | }
--------------------------------------------------------------------------------
/src/assets/login-person.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
192 |
--------------------------------------------------------------------------------