├── 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 | logo 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 |
37 | 38 | 39 |
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 | logo 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 | photo 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 | photo 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 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 45 | 65 | 70 | 75 | 76 | 78 | 79 | 81 | image/svg+xml 82 | 84 | 85 | 86 | 87 | 88 | 93 | 99 | 100 | 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 |
100 |
101 |
102 |
103 |
104 |
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 |
66 | 67 | 68 | 69 | 70 |
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 | imgBackground 78 | imgPerson 79 | screen1 80 | screen2 81 | screen3 82 | imgIcons 83 |
84 |
85 | imageform 86 |
87 |

Login Admin

88 | 89 |

{error}

90 | 91 | 92 |
93 | 94 |
95 | 96 |
97 | 98 |
99 | 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, {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 ? image : 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 |
{ this.updateCategory(e, data.sub_category_id) }}> 128 | {this.setState({ name: e.target.value })}} value={name} type="text" /> 129 | 130 |
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 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 54 | 57 | 61 | 65 | 66 | 76 | 79 | 83 | 87 | 88 | 89 | 107 | 109 | 110 | 112 | image/svg+xml 113 | 115 | 116 | 117 | 118 | 119 | 124 | 127 | 132 | 137 | 142 | 143 | 144 | 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 |
136 | 137 | 138 | 139 | 140 |
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 |
{ this.updateCategory(e, category.category_id) }}> 157 | {this.setState({ name: e.target.value })}} value={name} type="text"/> 158 | 159 |
160 |
161 | } 162 | 163 | { image ? image : '' } 164 | 165 | 166 | { //open input for add subcategory 167 | open_add ? '' : 168 |
169 |
{ this.addSubCategory(e, category.category_id) }}> 170 | 171 | {this.setState({ name: e.target.value })}} type="text"/> 172 | 173 |
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 |
130 |
131 |
this.setState({ change: false })} className="edit"> 132 | 133 |
134 |

Change Password

135 | 136 | {this.setState({ current_password: e.target.value })}} type="password"/> 137 | 138 | {this.setState({ new_password: e.target.value })}} type="password"/> 139 | 140 |
141 |
142 | } 143 | 144 | { 145 | edit ? 146 |
147 | { message ? {message} : '' } 148 |
this.setState({ edit: false })} className="edit"> 149 | 150 |
151 |
152 | admin 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 | admin 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 |
193 | this.setState({ search: e.target.value })} type="search"/> 194 | 195 |
196 |
197 |
198 | 199 | { // loading product 200 | loading ?
{ products.map(num=>
) }
: 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 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 63 | 70 | 77 | 84 | 91 | 96 | 97 | 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 | 20 | 22 | 32 | 35 | 39 | 43 | 44 | 54 | 57 | 61 | 65 | 66 | 67 | 85 | 87 | 88 | 90 | image/svg+xml 91 | 93 | 94 | 95 | 96 | 97 | 102 | 111 | 117 | 123 | 129 | 135 | 141 | 147 | 152 | 158 | 167 | 172 | 178 | 184 | 190 | 191 | 192 | --------------------------------------------------------------------------------