├── src ├── assets │ ├── product1.png │ ├── product2.png │ ├── DumbMerch.png │ ├── blank-profile.png │ └── empty.svg ├── fakeData │ ├── category.js │ ├── transaction.js │ ├── contact.js │ └── product.js ├── reportWebVitals.js ├── config │ └── api.js ├── components │ ├── card │ │ └── ProductCard.js │ ├── form │ │ └── CheckBox.js │ ├── modal │ │ └── DeleteData.js │ ├── complain │ │ ├── Contact.js │ │ └── Chat.js │ ├── Navbar.js │ ├── NavbarAdmin.js │ └── auth │ │ ├── Login.js │ │ └── Register.js ├── context │ └── userContext.js ├── index.js ├── pages │ ├── Auth.js │ ├── Product.js │ ├── AddCategoryAdmin.js │ ├── UpdateCategoryAdmin.js │ ├── DetailProduct.js │ ├── Complain.js │ ├── ComplainAdmin.js │ ├── CategoryAdmin.js │ ├── AddProductAdmin.js │ ├── Profile.js │ ├── ProductAdmin.js │ └── UpdateProductAdmin.js ├── App.js └── index.css ├── .gitignore ├── package.json └── public └── index.html /src/assets/product1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevinputra01/FrontEnd-Dumbmerch/HEAD/src/assets/product1.png -------------------------------------------------------------------------------- /src/assets/product2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevinputra01/FrontEnd-Dumbmerch/HEAD/src/assets/product2.png -------------------------------------------------------------------------------- /src/assets/DumbMerch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevinputra01/FrontEnd-Dumbmerch/HEAD/src/assets/DumbMerch.png -------------------------------------------------------------------------------- /src/assets/blank-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kevinputra01/FrontEnd-Dumbmerch/HEAD/src/assets/blank-profile.png -------------------------------------------------------------------------------- /src/fakeData/category.js: -------------------------------------------------------------------------------- 1 | let dataCategory = [ 2 | { 3 | id: 1, 4 | name: 'Mouse' 5 | }, 6 | { 7 | id: 2, 8 | name: 'Keyboard' 9 | }, 10 | { 11 | id: 3, 12 | name: 'Bag' 13 | }, 14 | { 15 | id: 4, 16 | name: 'Stationary' 17 | }, 18 | ] 19 | 20 | export default dataCategory -------------------------------------------------------------------------------- /.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/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/config/api.js: -------------------------------------------------------------------------------- 1 | export const API = () => { 2 | const baseUrl = "https://be-dumbmerch.herokuapp.com/api/v1" || "http://localhost:5000/api/v1"; 3 | 4 | const executeAPI = async (endpoint, config) => { 5 | const response = await fetch(baseUrl + endpoint, config); 6 | const data = await response.json(); 7 | return data; 8 | }; 9 | 10 | return { 11 | get: executeAPI, 12 | post: executeAPI, 13 | patch: executeAPI, 14 | delete: executeAPI, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/card/ProductCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import convertRupiah from "rupiah-format"; 5 | 6 | export default function ProductCard({ item, index }) { 7 | return ( 8 | 13 |
14 | 15 |
16 |
{item.name}
17 |
18 | {convertRupiah.convert(item.price)} 19 |
20 |
Stock : {item.qty}
21 |
22 |
23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/form/CheckBox.js: -------------------------------------------------------------------------------- 1 | import React, { createElement, useState, useEffect } from "react"; 2 | 3 | export default function CheckBox(props) { 4 | const { value, handleChangeCategoryId, categoryId } = props; 5 | 6 | const [isChecked, setIsChecked] = useState(); 7 | 8 | const getIsChecked = () => { 9 | if (categoryId?.length != 0) { 10 | categoryId?.every((item) => { 11 | if (item == value) { 12 | setIsChecked(true); 13 | return false; 14 | } else { 15 | setIsChecked(false); 16 | return true; 17 | } 18 | }); 19 | } else { 20 | setIsChecked(false); 21 | } 22 | }; 23 | 24 | useEffect(() => { 25 | getIsChecked(); 26 | }, [categoryId]); 27 | 28 | return createElement("input", { 29 | type: "checkbox", 30 | checked: isChecked, 31 | value: value, 32 | onClick: handleChangeCategoryId, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/context/userContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useReducer } from "react"; 2 | 3 | export const UserContext = createContext(); 4 | 5 | const initialState = { 6 | isLogin: false, 7 | user: {}, 8 | }; 9 | 10 | const reducer = (state, action) => { 11 | const { type, payload } = action; 12 | 13 | switch (type) { 14 | case "USER_SUCCESS": 15 | case "LOGIN_SUCCESS": 16 | localStorage.setItem("token", payload.token); 17 | return { 18 | isLogin: true, 19 | user: payload, 20 | }; 21 | case "AUTH_ERROR": 22 | case "LOGOUT": 23 | localStorage.removeItem("token"); 24 | return { 25 | isLogin: false, 26 | user: {}, 27 | }; 28 | default: 29 | throw new Error(); 30 | } 31 | }; 32 | 33 | export const UserContextProvider = ({ children }) => { 34 | const [state, dispatch] = useReducer(reducer, initialState); 35 | 36 | return ( 37 | 38 | {children} 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /src/components/modal/DeleteData.js: -------------------------------------------------------------------------------- 1 | import { Modal, Button } from 'react-bootstrap' 2 | 3 | export default function DeleteData({ show, handleClose, setConfirmDelete }) { 4 | 5 | const handleDelete = () => { 6 | setConfirmDelete(true) 7 | } 8 | 9 | return ( 10 | 11 | 12 |
13 | Delete Data 14 |
15 |
16 | Are you sure you want to delete this data? 17 |
18 |
19 | 20 | 21 |
22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/fakeData/transaction.js: -------------------------------------------------------------------------------- 1 | const dataTransaction = [ 2 | { 3 | id: 1, 4 | name: 'Mouse', 5 | date: new Date(), 6 | price: 500000, 7 | subtotal: 500000, 8 | img: 'https://images.unsplash.com/photo-1605773527852-c546a8584ea3?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=967&q=80', 9 | }, 10 | { 11 | id: 2, 12 | name: 'Keyboard', 13 | date: new Date(), 14 | price: 700000, 15 | subtotal: 700000, 16 | img: 'https://images.unsplash.com/photo-1573643808568-4a3c26f3a06b?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTZ8fGtleWJvYXJkfGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60', 17 | }, 18 | { 19 | id: 3, 20 | name: 'Monitor', 21 | date: new Date(), 22 | price: 2300000, 23 | subtotal: 2300000, 24 | img: 'https://images.unsplash.com/photo-1619953942547-233eab5a70d6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=658&q=80', 25 | }, 26 | ] 27 | 28 | export default dataTransaction -------------------------------------------------------------------------------- /src/components/complain/Contact.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import default_profile from "../../assets/blank-profile.png" 4 | 5 | export default function Contact({ dataContact, clickContact, contact }) { 6 | return ( 7 | <> 8 | {dataContact.length > 0 && ( 9 | <> 10 | {dataContact.map((item) => ( 11 |
{ 17 | clickContact(item); 18 | }} 19 | > 20 | user avatar 25 |
26 |

{item.name}

27 |

28 | {item.message} 29 |

30 |
31 |
32 | ))} 33 | 34 | )} 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { BrowserRouter as Router } from "react-router-dom"; 4 | // Init QueryClient and QueryClientProvider 5 | import { QueryClient, QueryClientProvider } from "react-query"; 6 | import "./index.css"; 7 | import App from "./App"; 8 | import reportWebVitals from "./reportWebVitals"; 9 | 10 | import "bootstrap/dist/css/bootstrap.min.css"; 11 | import { UserContextProvider } from "./context/userContext"; 12 | 13 | // favicon 14 | import Favicon from "./assets/DumbMerch.png"; 15 | const favicon = document.getElementById("idFavicon"); 16 | favicon.setAttribute("href", Favicon); 17 | 18 | // Init Client 19 | const client = new QueryClient(); 20 | 21 | ReactDOM.render( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | , 31 | document.getElementById("root") 32 | ); 33 | 34 | // If you want to start measuring performance in your app, pass a function 35 | // to log results (for example: reportWebVitals(console.log)) 36 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 37 | reportWebVitals(); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.14.1", 7 | "@testing-library/react": "^11.2.7", 8 | "@testing-library/user-event": "^12.8.3", 9 | "axios": "^0.21.1", 10 | "bootstrap": "^5.0.2", 11 | "dateformat": "^4.5.1", 12 | "react": "^17.0.2", 13 | "react-bootstrap": "^1.6.1", 14 | "react-dom": "^17.0.2", 15 | "react-masonry-css": "^1.0.16", 16 | "react-query": "^3.19.1", 17 | "react-router-dom": "^5.2.0", 18 | "react-scripts": "4.0.3", 19 | "react-show-more-text": "^1.4.6", 20 | "rupiah-format": "^1.0.0", 21 | "socket.io-client": "^4.1.3", 22 | "web-vitals": "^1.1.2" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/fakeData/contact.js: -------------------------------------------------------------------------------- 1 | const dataContact = [ 2 | { 3 | id: 1, 4 | name: "Customer 1", 5 | chat: "Yes, Is there anything I can help", 6 | img: "https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTh8fHVzZXJ8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60", 7 | }, 8 | { 9 | id: 2, 10 | name: "Customer 2", 11 | chat: "Hello World", 12 | img: "https://images.unsplash.com/photo-1604607055958-4def78942d6e?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MjZ8fHVzZXJ8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60", 13 | }, 14 | { 15 | id: 3, 16 | name: "Customer 3", 17 | chat: "Hello World Everyone", 18 | img: "https://images.unsplash.com/photo-1599110906885-b024c90c2773?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MzB8fHVzZXJ8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60", 19 | }, 20 | { 21 | id: 4, 22 | name: "Customer 4", 23 | chat: "I want complain", 24 | img: "https://images.unsplash.com/photo-1489980557514-251d61e3eeb6?ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mzh8fHVzZXJ8ZW58MHx8MHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60", 25 | }, 26 | { 27 | id: 5, 28 | name: "Customer 5", 29 | chat: "Hello Admin", 30 | img: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1050&q=80", 31 | }, 32 | ]; 33 | 34 | export default dataContact; 35 | -------------------------------------------------------------------------------- /src/components/complain/Chat.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import default_profile from '../../assets/blank-profile.png' 4 | 5 | export default function Chat({ contact, user, messages, sendMessage }) { 6 | return ( 7 | <> 8 | {contact ? ( 9 | <> 10 |
11 | {messages.map((item, index) => ( 12 |
13 |
14 | {item.idSender !== user.id && ( 15 | bubble avatar 16 | )} 17 |
20 | {item.message} 21 |
22 |
23 |
24 | ))} 25 |
26 |
27 | 31 |
32 | 33 | ) : ( 34 |
38 | No Message 39 |
40 | )} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react' 2 | import { Container, Navbar as NavbarComp, Nav, NavDropdown } from 'react-bootstrap' 3 | import { 4 | Link, 5 | useHistory 6 | } from "react-router-dom" 7 | 8 | import { UserContext } from '../context/userContext' 9 | 10 | import ImgDumbMerch from '../assets/DumbMerch.png' 11 | 12 | export default function Navbar(props) { 13 | const [state, dispatch] = useContext(UserContext) 14 | 15 | let history = useHistory() 16 | 17 | const logout = () => { 18 | console.log(state) 19 | dispatch({ 20 | type: "LOGOUT" 21 | }) 22 | history.push("/auth") 23 | } 24 | 25 | return ( 26 | 27 | 28 | 29 | brand 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 26 | React App 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/components/NavbarAdmin.js: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react' 2 | import { Container, Navbar as NavbarComp, Nav, NavDropdown } from 'react-bootstrap' 3 | import { 4 | Link, 5 | useHistory 6 | } from "react-router-dom" 7 | 8 | import { UserContext } from '../context/userContext' 9 | 10 | import ImgDumbMerch from '../assets/DumbMerch.png' 11 | 12 | export default function NavbarAdmin(props) { 13 | const [state, dispatch] = useContext(UserContext) 14 | 15 | let history = useHistory() 16 | 17 | const logout = () => { 18 | console.log(state) 19 | dispatch({ 20 | type: "LOGOUT" 21 | }) 22 | history.push("/auth") 23 | } 24 | 25 | return ( 26 | 27 | 28 | 29 | brand 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/pages/Auth.js: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { Container, Row, Col } from "react-bootstrap"; 3 | import { useHistory } from "react-router-dom"; 4 | 5 | import { UserContext } from "../context/userContext"; 6 | 7 | import ImgDumbMerch from "../assets/DumbMerch.png"; 8 | 9 | import Login from "../components/auth/Login"; 10 | import Register from "../components/auth/Register"; 11 | 12 | export default function Auth() { 13 | let history = useHistory(); 14 | 15 | const [state] = useContext(UserContext); 16 | 17 | console.log(state); 18 | 19 | const [isRegister, setIsRegister] = useState(false); 20 | 21 | const switchLogin = () => { 22 | setIsRegister(false); 23 | }; 24 | 25 | const switchRegister = () => { 26 | setIsRegister(true); 27 | }; 28 | 29 | return ( 30 |
31 | 32 | 33 | 34 | 39 |
Easy, Fast and Reliable
40 |

41 | Go shopping for merchandise, just go to dumb merch
{" "} 42 | shopping. the biggest merchandise in Indonesia 43 |

44 |
45 | 48 | 54 |
55 | 56 | {isRegister ? : } 57 |
58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/Product.js: -------------------------------------------------------------------------------- 1 | import { useContext, useState, useEffect } from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | import Masonry from "react-masonry-css"; 4 | import { Container, Row, Col } from "react-bootstrap"; 5 | 6 | import { UserContext } from "../context/userContext"; 7 | 8 | import Navbar from "../components/Navbar"; 9 | import ProductCard from "../components/card/ProductCard"; 10 | 11 | import imgEmpty from "../assets/empty.svg"; 12 | 13 | // Import useQuery 14 | import { useQuery } from "react-query"; 15 | 16 | // API config 17 | import { API } from "../config/api"; 18 | 19 | export default function Product() { 20 | let api = API(); 21 | 22 | const title = "Shop"; 23 | document.title = "DumbMerch | " + title; 24 | 25 | // Fetching product data from database 26 | let { data: products, refetch } = useQuery("productsCache", async () => { 27 | const config = { 28 | method: "GET", 29 | headers: { 30 | Authorization: "Basic " + localStorage.token, 31 | }, 32 | }; 33 | const response = await api.get("/products", config); 34 | return response.data; 35 | }); 36 | 37 | const breakpointColumnsObj = { 38 | default: 6, 39 | 1100: 4, 40 | 700: 3, 41 | 500: 2, 42 | }; 43 | 44 | return ( 45 |
46 | 47 | 48 | 49 | 50 |
Product
51 | 52 |
53 | 54 | {products?.length != 0 ? ( 55 | 60 | {products?.map((item, index) => ( 61 | 62 | ))} 63 | 64 | ) : ( 65 | 66 |
67 | 72 |
No data product
73 |
74 | 75 | )} 76 |
77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/pages/AddCategoryAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Container, Row, Col, Button } from "react-bootstrap"; 3 | import { useHistory } from "react-router"; 4 | 5 | import NavbarAdmin from "../components/NavbarAdmin"; 6 | 7 | import dataCategory from "../fakeData/category"; 8 | 9 | // Import useMutation 10 | import { useMutation } from "react-query"; 11 | 12 | // Import API config 13 | import { API } from "../config/api"; 14 | 15 | export default function AddCategoryAdmin() { 16 | console.clear(); 17 | 18 | const title = "Category admin"; 19 | document.title = "DumbMerch | " + title; 20 | 21 | let history = useHistory(); 22 | let api = API(); 23 | 24 | const [category, setCategory] = useState(""); 25 | 26 | const handleChange = (e) => { 27 | setCategory(e.target.value); 28 | }; 29 | 30 | const handleSubmit = useMutation(async (e) => { 31 | try { 32 | e.preventDefault(); 33 | 34 | // Data body 35 | const body = JSON.stringify({ name: category }); 36 | 37 | // Configuration 38 | const config = { 39 | method: "POST", 40 | headers: { 41 | "Content-type": "application/json", 42 | }, 43 | body, 44 | }; 45 | console.log(config); 46 | 47 | // Insert category data 48 | const response = await api.post("/category", config); 49 | 50 | console.log(response); 51 | 52 | history.push("/category-admin"); 53 | } catch (error) { 54 | console.log(error); 55 | } 56 | }); 57 | 58 | return ( 59 | <> 60 | 61 | 62 | 63 | 64 |
Add Category
65 | 66 | 67 |
handleSubmit.mutate(e)}> 68 | 75 |
76 | 79 |
80 |
81 | 82 |
83 |
84 | 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /src/pages/UpdateCategoryAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Container, Row, Col, Button } from "react-bootstrap"; 3 | import { useParams, useHistory } from "react-router"; 4 | 5 | import NavbarAdmin from "../components/NavbarAdmin"; 6 | 7 | import dataCategory from "../fakeData/category"; 8 | 9 | // Import useQuery and useMutation 10 | import { useQuery, useMutation } from "react-query"; 11 | 12 | // Get API config 13 | import { API } from "../config/api"; 14 | 15 | export default function UpdateCategoryAdmin() { 16 | const title = "Category admin"; 17 | document.title = "DumbMerch | " + title; 18 | 19 | let history = useHistory(); 20 | let api = API(); 21 | const { id } = useParams(); 22 | 23 | const [category, setCategory] = useState({ name: "" }); 24 | 25 | // Fetching category data by id from database 26 | let { refetch } = useQuery("categoryCache", async () => { 27 | const response = await api.get("/category/" + id); 28 | setCategory({ name: response.data.name }); 29 | }); 30 | 31 | const handleChange = (e) => { 32 | setCategory({ 33 | ...category, 34 | name: e.target.value, 35 | }); 36 | }; 37 | 38 | const handleSubmit = useMutation(async (e) => { 39 | try { 40 | e.preventDefault(); 41 | 42 | // Data body 43 | const body = JSON.stringify(category); 44 | 45 | // Configuration 46 | const config = { 47 | method: "PATCH", 48 | headers: { 49 | "Content-type": "application/json", 50 | }, 51 | body, 52 | }; 53 | 54 | // Insert category data 55 | const response = await api.patch("/category/" + id, config); 56 | 57 | history.push("/category-admin"); 58 | } catch (error) { 59 | console.log(error); 60 | } 61 | }); 62 | 63 | return ( 64 | <> 65 | 66 | 67 | 68 | 69 |
Edit Category
70 | 71 | 72 |
handleSubmit.mutate(e)}> 73 | 79 |
80 | 83 |
84 |
85 | 86 |
87 |
88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/fakeData/product.js: -------------------------------------------------------------------------------- 1 | const dataProduct = [ 2 | { 3 | id: 1, 4 | name: 'Mouse', 5 | price: '500000', 6 | stock: 600, 7 | url: 'https://images.unsplash.com/photo-1605773527852-c546a8584ea3?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=967&q=80', 8 | desc: 'Mouse Wireless Alytech AL - Y5D, hadir dengan desain 3 tombol mouse yang ringan dan mudah dibawa. Mouse ini menggunakan frekuensi radio 2.4GHz (bekerja hingga jarak 10m) dan fitur sensor canggih optik pelacakan dengan penerima USB yang kecil. Mouse ini didukung oleh 1x baterai AA (hingga 12 bulan hidup baterai). mendukung sistem operasi Windows 7,8, 10 keatas, Mac OS X 10.8 atau yang lebih baru dan sistem operasi Chrome OS.' 9 | }, 10 | { 11 | id: 2, 12 | name: 'Keyboard', 13 | price: '700000', 14 | stock: 300, 15 | url: 'https://images.unsplash.com/photo-1573643808568-4a3c26f3a06b?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTZ8fGtleWJvYXJkfGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60', 16 | desc: 'Mouse Wireless Alytech AL - Y5D, hadir dengan desain 3 tombol mouse yang ringan dan mudah dibawa. Mouse ini menggunakan frekuensi radio 2.4GHz (bekerja hingga jarak 10m) dan fitur sensor canggih optik pelacakan dengan penerima USB yang kecil. Mouse ini didukung oleh 1x baterai AA (hingga 12 bulan hidup baterai). mendukung sistem operasi Windows 7,8, 10 keatas, Mac OS X 10.8 atau yang lebih baru dan sistem operasi Chrome OS.' 17 | }, 18 | { 19 | id: 3, 20 | name: 'Monitor', 21 | price: '2300000', 22 | stock: 250, 23 | url: 'https://images.unsplash.com/photo-1619953942547-233eab5a70d6?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=658&q=80', 24 | desc: 'Mouse Wireless Alytech AL - Y5D, hadir dengan desain 3 tombol mouse yang ringan dan mudah dibawa. Mouse ini menggunakan frekuensi radio 2.4GHz (bekerja hingga jarak 10m) dan fitur sensor canggih optik pelacakan dengan penerima USB yang kecil. Mouse ini didukung oleh 1x baterai AA (hingga 12 bulan hidup baterai). mendukung sistem operasi Windows 7,8, 10 keatas, Mac OS X 10.8 atau yang lebih baru dan sistem operasi Chrome OS.' 25 | }, 26 | { 27 | id: 4, 28 | name: 'Drone', 29 | price: '9500000', 30 | stock: 25, 31 | url: 'https://images.unsplash.com/photo-1555009306-9e3c5b6a66e3?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MzJ8fGRyb25lfGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60', 32 | desc: 'Mouse Wireless Alytech AL - Y5D, hadir dengan desain 3 tombol mouse yang ringan dan mudah dibawa. Mouse ini menggunakan frekuensi radio 2.4GHz (bekerja hingga jarak 10m) dan fitur sensor canggih optik pelacakan dengan penerima USB yang kecil. Mouse ini didukung oleh 1x baterai AA (hingga 12 bulan hidup baterai). mendukung sistem operasi Windows 7,8, 10 keatas, Mac OS X 10.8 atau yang lebih baru dan sistem operasi Chrome OS.' 33 | } 34 | ] 35 | 36 | export default dataProduct -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useContext, useState, useEffect } from "react"; 2 | import { Switch, Route, useHistory } from "react-router-dom"; 3 | import { UserContext } from "./context/userContext"; 4 | 5 | import Auth from "./pages/Auth"; 6 | import Product from "./pages/Product"; 7 | import DetailProduct from "./pages/DetailProduct"; 8 | import Complain from "./pages/Complain"; 9 | import Profile from "./pages/Profile"; 10 | import ComplainAdmin from "./pages/ComplainAdmin"; 11 | import CategoryAdmin from "./pages/CategoryAdmin"; 12 | import ProductAdmin from "./pages/ProductAdmin"; 13 | import UpdateCategoryAdmin from "./pages/UpdateCategoryAdmin"; 14 | import AddCategoryAdmin from "./pages/AddCategoryAdmin"; 15 | import AddProductAdmin from "./pages/AddProductAdmin"; 16 | import UpdateProductAdmin from "./pages/UpdateProductAdmin"; 17 | 18 | import { API } from "./config/api"; 19 | 20 | function App() { 21 | let api = API(); 22 | let history = useHistory(); 23 | const [state, dispatch] = useContext(UserContext); 24 | 25 | useEffect(() => { 26 | // Redirect Auth 27 | if (state.isLogin == false) { 28 | history.push("/auth"); 29 | } else { 30 | if (state.user.status == "admin") { 31 | history.push("/complain-admin"); 32 | // history.push("/complain-admin"); 33 | } else if (state.user.status == "customer") { 34 | history.push("/"); 35 | } 36 | } 37 | }, [state]); 38 | 39 | const checkUser = async () => { 40 | try { 41 | const config = { 42 | method: "GET", 43 | headers: { 44 | Authorization: "Basic " + localStorage.token, 45 | }, 46 | }; 47 | const response = await api.get("/check-auth", config); 48 | 49 | // If the token incorrect 50 | if (response.status === "failed") { 51 | return dispatch({ 52 | type: "AUTH_ERROR", 53 | }); 54 | } 55 | 56 | // // Get user data 57 | let payload = response.data.user; 58 | // // Get token from local storage 59 | payload.token = localStorage.token; 60 | 61 | // // Send data to useContext 62 | dispatch({ 63 | type: "USER_SUCCESS", 64 | payload, 65 | }); 66 | } catch (error) { 67 | console.log(error); 68 | } 69 | }; 70 | 71 | useEffect(() => { 72 | checkUser(); 73 | }, []); 74 | 75 | return ( 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | ); 91 | } 92 | 93 | export default App; 94 | -------------------------------------------------------------------------------- /src/components/auth/Login.js: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { UserContext } from "../../context/userContext"; 3 | import { useHistory } from "react-router-dom"; 4 | import { Alert } from "react-bootstrap"; 5 | 6 | // Import useMutation 7 | import { useMutation } from "react-query"; 8 | 9 | // Import API config 10 | import { API } from "../../config/api"; 11 | 12 | export default function Login() { 13 | const title = "Login"; 14 | document.title = "DumbMerch | " + title; 15 | 16 | let history = useHistory(); 17 | let api = API(); 18 | 19 | const [state, dispatch] = useContext(UserContext); 20 | 21 | const [message, setMessage] = useState(null); 22 | const [form, setForm] = useState({ 23 | email: "", 24 | password: "", 25 | }); 26 | 27 | const { email, password } = form; 28 | 29 | const handleChange = (e) => { 30 | setForm({ 31 | ...form, 32 | [e.target.name]: e.target.value, 33 | }); 34 | }; 35 | 36 | const handleSubmit = useMutation(async (e) => { 37 | try { 38 | e.preventDefault(); 39 | 40 | // Data body 41 | const body = JSON.stringify(form); 42 | 43 | // Configuration 44 | const config = { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | body: body, 50 | }; 51 | 52 | // Insert data for login process 53 | const response = await api.post("/login", config); 54 | 55 | console.log(response); 56 | 57 | // Checking process 58 | if (response.status == "success") { 59 | // Send data to useContext 60 | dispatch({ 61 | type: "LOGIN_SUCCESS", 62 | payload: response.data, 63 | }); 64 | 65 | // Status check 66 | if (response.data.status == "admin") { 67 | history.push("/complain-admin"); 68 | } else { 69 | history.push("/"); 70 | } 71 | 72 | const alert = ( 73 | 74 | Login success 75 | 76 | ); 77 | setMessage(alert); 78 | } else { 79 | const alert = ( 80 | 81 | Login failed 82 | 83 | ); 84 | setMessage(alert); 85 | } 86 | } catch (error) { 87 | const alert = ( 88 | 89 | Login failed 90 | 91 | ); 92 | setMessage(alert); 93 | console.log(error); 94 | } 95 | }); 96 | 97 | return ( 98 |
99 |
100 |
104 | Login 105 |
106 | {message && message} 107 |
handleSubmit.mutate(e)}> 108 |
109 | 117 | 125 |
126 |
127 | 128 |
129 |
130 |
131 |
132 | ); 133 | } 134 | -------------------------------------------------------------------------------- /src/components/auth/Register.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { UserContext } from "../../context/userContext"; 3 | import { useHistory } from "react-router-dom"; 4 | import { Alert } from "react-bootstrap"; 5 | 6 | import { useMutation } from "react-query"; 7 | 8 | import { API } from "../../config/api"; 9 | 10 | export default function Register() { 11 | const title = "Register"; 12 | document.title = "DumbMerch | " + title; 13 | 14 | let history = useHistory(); 15 | let api = API(); 16 | 17 | const [state, dispatch] = useContext(UserContext); 18 | 19 | const [message, setMessage] = useState(null); 20 | const [form, setForm] = useState({ 21 | name: "", 22 | email: "", 23 | password: "", 24 | }); 25 | 26 | const { name, email, password } = form; 27 | 28 | const handleChange = (e) => { 29 | setForm({ 30 | ...form, 31 | [e.target.name]: e.target.value, 32 | }); 33 | }; 34 | 35 | const handleSubmit = useMutation(async (e) => { 36 | try { 37 | e.preventDefault(); 38 | 39 | // Data body 40 | const body = JSON.stringify(form); 41 | 42 | // Configuration Content-type 43 | const config = { 44 | method: "POST", 45 | headers: { 46 | "Content-Type": "application/json", 47 | }, 48 | body: body, 49 | }; 50 | 51 | // Insert data user to database 52 | const response = await api.post("/register", config); 53 | 54 | console.log(response); 55 | 56 | // Notification 57 | if (response.status == "success") { 58 | const alert = ( 59 | 60 | Success 61 | 62 | ); 63 | setMessage(alert); 64 | setForm({ 65 | name: "", 66 | email: "", 67 | password: "", 68 | }); 69 | } else { 70 | const alert = ( 71 | 72 | Failed 73 | 74 | ); 75 | setMessage(alert); 76 | } 77 | } catch (error) { 78 | const alert = ( 79 | 80 | Failed 81 | 82 | ); 83 | setMessage(alert); 84 | console.log(error); 85 | } 86 | }); 87 | 88 | return ( 89 |
90 |
91 |
95 | Register 96 |
97 | {message && message} 98 |
handleSubmit.mutate(e)}> 99 |
100 | 108 | 116 | 124 |
125 |
126 | 129 |
130 |
131 |
132 |
133 | ); 134 | } 135 | -------------------------------------------------------------------------------- /src/pages/DetailProduct.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useParams, useHistory } from "react-router-dom"; 3 | import { Container, Row, Col } from "react-bootstrap"; 4 | import convertRupiah from "rupiah-format"; 5 | 6 | import Navbar from "../components/Navbar"; 7 | 8 | import dataProduct from "../fakeData/product"; 9 | 10 | // Import useQuery and useMutation 11 | import { useQuery, useMutation } from "react-query"; 12 | 13 | // API config 14 | import { API } from "../config/api"; 15 | 16 | export default function DetailProduct() { 17 | let history = useHistory(); 18 | let { id } = useParams(); 19 | let api = API(); 20 | 21 | // Fetching product data from database 22 | let { data: product, refetch } = useQuery("Cache", async () => { 23 | const config = { 24 | method: "GET", 25 | headers: { 26 | Authorization: "Basic " + localStorage.token, 27 | }, 28 | }; 29 | const response = await api.get("/product/" + id, config); 30 | return response.data; 31 | }); 32 | 33 | useEffect(() => { 34 | //change this to the script source you want to load, for example this is snap.js sandbox env 35 | const midtransScriptUrl = "https://app.sandbox.midtrans.com/snap/snap.js"; 36 | //change this according to your client-key 37 | const myMidtransClientKey = "SB-Mid-client-omo-P8HmHl_eNjFP"; 38 | 39 | let scriptTag = document.createElement("script"); 40 | scriptTag.src = midtransScriptUrl; 41 | // optional if you want to set script attribute 42 | // for example snap.js have data-client-key attribute 43 | scriptTag.setAttribute("data-client-key", myMidtransClientKey); 44 | 45 | document.body.appendChild(scriptTag); 46 | return () => { 47 | document.body.removeChild(scriptTag); 48 | }; 49 | }, []); 50 | 51 | const handleBuy = useMutation(async () => { 52 | try { 53 | // Get data from product 54 | const data = { 55 | idProduct: product.id, 56 | idSeller: product.user.id, 57 | price: product.price, 58 | }; 59 | 60 | // Data body 61 | const body = JSON.stringify(data); 62 | 63 | // Configuration 64 | const config = { 65 | method: "POST", 66 | headers: { 67 | Authorization: "Basic " + localStorage.token, 68 | "Content-type": "application/json", 69 | }, 70 | body, 71 | }; 72 | 73 | // Insert transaction data 74 | const response = await api.post("/transaction", config); 75 | console.log(response); 76 | const token = response.payment.token; 77 | 78 | window.snap.pay(token, { 79 | onSuccess: function (result) { 80 | console.log(result); 81 | history.push("/profile"); 82 | }, 83 | onPending: function (result) { 84 | console.log(result); 85 | history.push("/profile"); 86 | }, 87 | onError: function (result) { 88 | console.log(result); 89 | }, 90 | onClose: function () { 91 | alert("you closed the popup without finishing the payment"); 92 | }, 93 | }); 94 | } catch (error) { 95 | console.log(error); 96 | } 97 | }); 98 | 99 | return ( 100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
{product?.name}
110 |
111 | Stock : {product?.qty} 112 |
113 |

{product?.desc}

114 |
115 | {convertRupiah.convert(product?.price)} 116 |
117 |
118 | 124 |
125 | 126 |
127 |
128 |
129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /src/pages/Complain.js: -------------------------------------------------------------------------------- 1 | // import useContext hook 2 | import React, { useState, useEffect, useContext } from "react"; 3 | import { Container, Row, Col } from "react-bootstrap"; 4 | 5 | import Navbar from "../components/Navbar"; 6 | 7 | import Contact from "../components/complain/Contact"; 8 | 9 | // import chat component 10 | import Chat from "../components/complain/Chat"; 11 | 12 | // import user context 13 | import { UserContext } from "../context/userContext"; 14 | 15 | // import socket.io-client 16 | import { io } from "socket.io-client"; 17 | 18 | // initial variable outside socket 19 | let socket; 20 | export default function Complain() { 21 | const [contact, setContact] = useState(null); 22 | const [contacts, setContacts] = useState([]); 23 | // create messages state 24 | const [messages, setMessages] = useState([]); 25 | 26 | const title = "Complain admin"; 27 | document.title = "DumbMerch | " + title; 28 | 29 | // consume user context 30 | const [state] = useContext(UserContext); 31 | 32 | useEffect(() => { 33 | socket = io("http://localhost:5000", { 34 | auth: { 35 | token: localStorage.getItem("token"), 36 | }, 37 | query: { 38 | id: state.user.id, 39 | }, 40 | }); 41 | 42 | // define corresponding socket listener 43 | socket.on("new message", () => { 44 | console.log("contact", contact); 45 | console.log("triggered", contact?.id); 46 | socket.emit("load messages", contact?.id); 47 | }); 48 | 49 | // listen error sent from server 50 | socket.on("connect_error", (err) => { 51 | console.error(err.message); // not authorized 52 | }); 53 | loadContact(); 54 | loadMessages(); 55 | 56 | return () => { 57 | socket.disconnect(); 58 | }; 59 | }, [messages]); 60 | 61 | const loadContact = () => { 62 | // emit event load admin contact 63 | socket.emit("load admin contact"); 64 | // listen event to get admin contact 65 | socket.on("admin contact", async (data) => { 66 | // manipulate data to add message property with the newest message 67 | const dataContact = { 68 | ...data, 69 | message: 70 | messages.length > 0 71 | ? messages[messages.length - 1].message 72 | : "Click here to start message", 73 | }; 74 | setContacts([dataContact]); 75 | }); 76 | }; 77 | 78 | // used for active style when click contact 79 | const onClickContact = (data) => { 80 | setContact(data); 81 | // emit event load messages 82 | socket.emit("load messages", data.id); 83 | }; 84 | 85 | const loadMessages = (value) => { 86 | // define listener on event "messages" 87 | socket.on("messages", async (data) => { 88 | // get data messages 89 | if (data.length > 0) { 90 | const dataMessages = data.map((item) => ({ 91 | idSender: item.sender.id, 92 | message: item.message, 93 | })); 94 | console.log(dataMessages); 95 | setMessages(dataMessages); 96 | } 97 | const chatMessagesElm = document.getElementById("chat-messages"); 98 | chatMessagesElm.scrollTop = chatMessagesElm?.scrollHeight; 99 | }); 100 | }; 101 | const onSendMessage = (e) => { 102 | // listen only enter key event press 103 | if (e.key === "Enter") { 104 | const data = { 105 | idRecipient: contact.id, 106 | message: e.target.value, 107 | }; 108 | 109 | //emit event send message 110 | socket.emit("send message", data); 111 | e.target.value = ""; 112 | } 113 | }; 114 | 115 | return ( 116 | <> 117 | 118 | 119 | 120 | 125 | 130 | 131 | 132 | 138 | 139 | 140 | 141 | 142 | ); 143 | } 144 | -------------------------------------------------------------------------------- /src/pages/ComplainAdmin.js: -------------------------------------------------------------------------------- 1 | // import useContext hook 2 | import React, { useState, useEffect, useContext } from 'react' 3 | import { Container, Row, Col } from 'react-bootstrap' 4 | 5 | import NavbarAdmin from '../components/NavbarAdmin' 6 | 7 | import Contact from '../components/complain/Contact' 8 | // import chat component 9 | import Chat from '../components/complain/Chat' 10 | 11 | // import user context 12 | import { UserContext } from '../context/userContext' 13 | 14 | // import socket.io-client 15 | import {io} from 'socket.io-client' 16 | 17 | // initial variable outside socket 18 | let socket 19 | export default function ComplainAdmin() { 20 | const [contact, setContact] = useState(null) 21 | const [contacts, setContacts] = useState([]) 22 | // create messages state 23 | const [messages, setMessages] = useState([]) 24 | 25 | const title = "Complain admin" 26 | document.title = 'DumbMerch | ' + title 27 | 28 | // consume user context 29 | const [state] = useContext(UserContext) 30 | 31 | useEffect(() =>{ 32 | socket = io('http://localhost:5000', { 33 | auth: { 34 | token: localStorage.getItem('token') 35 | }, 36 | query: { 37 | id: state.user.id 38 | } 39 | }) 40 | 41 | // define listener for every updated message 42 | socket.on("new message", () => { 43 | console.log("contact", contact) 44 | socket.emit("load messages", contact?.id) 45 | }) 46 | 47 | loadContacts() 48 | loadMessages() 49 | 50 | return () => { 51 | socket.disconnect() 52 | } 53 | }, [messages]) 54 | 55 | const loadContacts = () => { 56 | socket.emit("load customer contacts") 57 | socket.on("customer contacts", (data) => { 58 | // filter just customers which have sent a message 59 | let dataContacts = data.filter(item => (item.status !== "admin") && (item.recipientMessage.length > 0 || item.senderMessage.length > 0)) 60 | 61 | // manipulate customers to add message property with the newest message 62 | dataContacts = dataContacts.map((item) => ({ 63 | ...item, 64 | message: item.senderMessage.length > 0 ? item.senderMessage[item.senderMessage.length -1].message : "Click here to start message" 65 | })) 66 | setContacts(dataContacts) 67 | }) 68 | } 69 | 70 | // used for active style when click contact 71 | const onClickContact = (data) => { 72 | setContact(data) 73 | // emit event load messages 74 | socket.emit("load messages", data.id) 75 | } 76 | 77 | const loadMessages = (value) => { 78 | // define listener on event "messages" 79 | socket.on("messages", (data) => { 80 | // get data messages 81 | if (data.length > 0) { 82 | const dataMessages = data.map((item) => ({ 83 | idSender: item.sender.id, 84 | message: item.message, 85 | })) 86 | setMessages(dataMessages) 87 | } 88 | loadContacts() 89 | const chatMessagesElm = document.getElementById("chat-messages"); 90 | chatMessagesElm.scrollTop = chatMessagesElm?.scrollHeight; 91 | }) 92 | } 93 | 94 | const onSendMessage = (e) => { 95 | // listen only enter key event press 96 | if(e.key === 'Enter') { 97 | const data = { 98 | idRecipient: contact.id, 99 | message: e.target.value 100 | } 101 | 102 | //emit event send message 103 | socket.emit("send message", data) 104 | e.target.value = "" 105 | } 106 | } 107 | 108 | return ( 109 | <> 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | ) 123 | } 124 | -------------------------------------------------------------------------------- /src/pages/CategoryAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Container, Row, Col, Table, Button } from "react-bootstrap"; 3 | import { useHistory } from "react-router"; 4 | 5 | import NavbarAdmin from "../components/NavbarAdmin"; 6 | import DeleteData from "../components/modal/DeleteData"; 7 | 8 | import dataCategory from "../fakeData/category"; 9 | 10 | import imgEmpty from "../assets/empty.svg"; 11 | 12 | // Import useQuery and useMutation 13 | import { useQuery, useMutation } from "react-query"; 14 | 15 | // Get API config 16 | import { API } from "../config/api"; 17 | 18 | export default function CategoryAdmin() { 19 | const title = "Category admin"; 20 | document.title = "DumbMerch | " + title; 21 | 22 | let history = useHistory(); 23 | let api = API(); 24 | 25 | // Variabel for delete category data 26 | const [idDelete, setIdDelete] = useState(null); 27 | const [confirmDelete, setConfirmDelete] = useState(null); 28 | 29 | const [show, setShow] = useState(false); 30 | const handleClose = () => setShow(false); 31 | const handleShow = () => setShow(true); 32 | 33 | // Fetching categories data from database 34 | let { data: categories, refetch } = useQuery("categoriesCache", async () => { 35 | const response = await api.get("/categories"); 36 | return response.data; 37 | }); 38 | 39 | const handleEdit = (id) => { 40 | history.push(`edit-category/${id}`); 41 | }; 42 | 43 | // For get id category & show modal confirm delete data 44 | const handleDelete = (id) => { 45 | setIdDelete(id); 46 | handleShow(); 47 | }; 48 | 49 | // If confirm is true, execute delete data 50 | const deleteById = useMutation(async (id) => { 51 | try { 52 | const config = { 53 | method: "DELETE", 54 | headers: { 55 | Authorization: "Basic " + localStorage.token, 56 | }, 57 | }; 58 | await api.delete(`/category/${id}`, config); 59 | refetch(); 60 | } catch (error) { 61 | console.log(error); 62 | } 63 | }); 64 | 65 | useEffect(() => { 66 | if (confirmDelete) { 67 | // Close modal confirm delete data 68 | handleClose(); 69 | // execute delete data by id function 70 | deleteById.mutate(idDelete); 71 | setConfirmDelete(null); 72 | } 73 | }, [confirmDelete]); 74 | 75 | const addCategory = () => { 76 | history.push("/add-category"); 77 | }; 78 | 79 | return ( 80 | <> 81 | 82 | 83 | 84 | 85 | 86 |
List Category
87 | 88 | 89 | 96 | 97 | 98 | {categories?.length != 0 ? ( 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | {categories?.map((item, index) => ( 109 | 110 | 113 | 116 | 136 | 137 | ))} 138 | 139 |
NoCategory NameAction
111 | {index + 1} 112 | 114 | {item.name} 115 | 117 | 126 | 135 |
140 | ) : ( 141 |
142 | 147 |
No data category
148 |
149 | )} 150 | 151 |
152 |
153 | 158 | 159 | ); 160 | } 161 | -------------------------------------------------------------------------------- /src/pages/AddProductAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Container, Row, Col, Button } from "react-bootstrap"; 3 | import { useHistory } from "react-router"; 4 | 5 | import NavbarAdmin from "../components/NavbarAdmin"; 6 | 7 | // Import useQuery & useMutation 8 | import { useQuery, useMutation } from "react-query"; 9 | 10 | // Import API config 11 | import { API } from "../config/api"; 12 | 13 | export default function AddProductAdmin() { 14 | const title = "Product admin"; 15 | document.title = "DumbMerch | " + title; 16 | 17 | let history = useHistory(); 18 | let api = API(); 19 | 20 | // const [categories, setCategories] = useState([]); //Store all category data 21 | const [categoryId, setCategoryId] = useState([]); //Save the selected category id 22 | const [preview, setPreview] = useState(null); //For image preview 23 | const [form, setForm] = useState({ 24 | image: "", 25 | name: "", 26 | desc: "", 27 | price: "", 28 | qty: "", 29 | }); //Store product data 30 | 31 | // Fetching category data 32 | let { data: categories, refetch } = useQuery("categoriesCache", async () => { 33 | const response = await api.get("/categories"); 34 | return response.data; 35 | }); 36 | 37 | // For handle if category selected 38 | const handleChangeCategoryId = (e) => { 39 | const id = e.target.value; 40 | const checked = e.target.checked; 41 | 42 | if (checked == true) { 43 | // Save category id if checked 44 | setCategoryId([...categoryId, parseInt(id)]); 45 | } else { 46 | // Delete category id from variable if unchecked 47 | let newCategoryId = categoryId.filter((categoryIdItem) => { 48 | return categoryIdItem != id; 49 | }); 50 | setCategoryId(newCategoryId); 51 | } 52 | }; 53 | 54 | // Handle change data on form 55 | const handleChange = (e) => { 56 | setForm({ 57 | ...form, 58 | [e.target.name]: 59 | e.target.type === "file" ? e.target.files : e.target.value, 60 | }); 61 | 62 | // Create image url for preview 63 | if (e.target.type === "file") { 64 | let url = URL.createObjectURL(e.target.files[0]); 65 | setPreview(url); 66 | } 67 | }; 68 | 69 | const handleSubmit = useMutation(async (e) => { 70 | try { 71 | e.preventDefault(); 72 | 73 | // Store data with FormData as object 74 | const formData = new FormData(); 75 | formData.set("image", form?.image[0], form?.image[0]?.name); 76 | formData.set("name", form.name); 77 | formData.set("desc", form.desc); 78 | formData.set("price", form.price); 79 | formData.set("qty", form.qty); 80 | formData.set("categoryId", categoryId); 81 | 82 | // Configuration 83 | const config = { 84 | method: "POST", 85 | headers: { 86 | Authorization: "Basic " + localStorage.token, 87 | }, 88 | body: formData, 89 | }; 90 | 91 | // Insert product data 92 | const response = await api.post("/product", config); 93 | 94 | history.push("/product-admin"); 95 | } catch (error) { 96 | console.log(error); 97 | } 98 | }); 99 | 100 | return ( 101 | <> 102 | 103 | 104 | 105 | 106 |
Add Product
107 | 108 | 109 |
handleSubmit.mutate(e)}> 110 | {preview && ( 111 |
112 | 120 |
121 | )} 122 | 129 | 132 | 139 | 146 | 153 | 160 |
161 |
165 | Category 166 |
167 | {categories?.map((item) => ( 168 | 176 | ))} 177 |
178 | 179 |
180 | 183 |
184 |
185 | 186 |
187 |
188 | 189 | ); 190 | } 191 | -------------------------------------------------------------------------------- /src/pages/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState, useEffect } from "react"; 2 | import { Container, Row, Col } from "react-bootstrap"; 3 | import dateFormat from "dateformat"; 4 | import convertRupiah from "rupiah-format"; 5 | 6 | import Navbar from "../components/Navbar"; 7 | 8 | import imgDumbMerch from "../assets/DumbMerch.png"; 9 | 10 | import { UserContext } from "../context/userContext"; 11 | import dataTransaction from "../fakeData/transaction"; 12 | 13 | import imgBlank from "../assets/blank-profile.png"; 14 | 15 | // Import useQuery 16 | import { useQuery } from "react-query"; 17 | 18 | // API config 19 | import { API } from "../config/api"; 20 | 21 | export default function Profile() { 22 | const title = "Profile"; 23 | document.title = "DumbMerch | " + title; 24 | 25 | let api = API(); 26 | 27 | const [state] = useContext(UserContext); 28 | 29 | // Fetching profile data from database 30 | let { data: profile, refetch: profileRefetch } = useQuery( 31 | "profileCache", 32 | async () => { 33 | const config = { 34 | method: "GET", 35 | headers: { 36 | Authorization: "Basic " + localStorage.token, 37 | }, 38 | }; 39 | const response = await api.get("/profile", config); 40 | return response.data; 41 | } 42 | ); 43 | 44 | // Fetching transactions data from database 45 | let { data: transactions, refetch: transactionsRefetch } = useQuery( 46 | "transactionsCache", 47 | async () => { 48 | const config = { 49 | method: "GET", 50 | headers: { 51 | Authorization: "Basic " + localStorage.token, 52 | }, 53 | }; 54 | const response = await api.get("/transactions", config); 55 | return response.data; 56 | } 57 | ); 58 | 59 | return ( 60 | <> 61 | 62 | 63 | 64 | 65 |
My Profile
66 | 67 | 68 | profile 73 | 74 | 75 |
Name
76 |
{state.user.name}
77 | 78 |
Email
79 |
{state.user.email}
80 | 81 |
Phone
82 |
83 | {profile?.phone ? profile?.phone : "-"} 84 |
85 | 86 |
Gender
87 |
88 | {profile?.gender ? profile?.gender : "-"} 89 |
90 | 91 |
Address
92 |
93 | {profile?.address ? profile?.address : "-"} 94 |
95 | 96 |
97 | 98 | 99 |
My Transaction
100 | {transactions?.length != 0 ? ( 101 | <> 102 | {transactions?.map((item) => ( 103 |
104 | 105 | 106 | 107 | img 117 | 118 | 119 |
127 | {item.product.name} 128 |
129 |
138 | {dateFormat( 139 | item.createdAt, 140 | "dddd, d mmmm yyyy, HH:MM " 141 | )} 142 | WIB 143 |
144 | 145 |
152 | Price : {convertRupiah.convert(item.price)} 153 |
154 | 155 |
162 | Sub Total : {convertRupiah.convert(item.price)} 163 |
164 | 165 | 166 |
169 | {item.status} 170 |
171 | 172 |
173 |
174 |
175 | ))} 176 | 177 | ) : ( 178 |
No transaction
179 | )} 180 | 181 |
182 |
183 | 184 | ); 185 | } 186 | -------------------------------------------------------------------------------- /src/pages/ProductAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Container, Row, Col, Table, Button } from "react-bootstrap"; 3 | import { useHistory } from "react-router"; 4 | import ShowMoreText from "react-show-more-text"; 5 | import rupiahFormat from "rupiah-format"; 6 | 7 | import NavbarAdmin from "../components/NavbarAdmin"; 8 | import DeleteData from "../components/modal/DeleteData"; 9 | 10 | import imgEmpty from "../assets/empty.svg"; 11 | 12 | import dataProduct from "../fakeData/product"; 13 | 14 | // Import useQuery and useMutation 15 | import { useQuery, useMutation } from "react-query"; 16 | 17 | // API config 18 | import { API } from "../config/api"; 19 | 20 | export default function ProductAdmin() { 21 | let history = useHistory(); 22 | let api = API(); 23 | 24 | // Variabel for delete product data 25 | const [idDelete, setIdDelete] = useState(null); 26 | const [confirmDelete, setConfirmDelete] = useState(null); 27 | 28 | // Modal Confirm delete data 29 | const [show, setShow] = useState(false); 30 | const handleClose = () => setShow(false); 31 | const handleShow = () => setShow(true); 32 | 33 | const title = "Product admin"; 34 | document.title = "DumbMerch | " + title; 35 | 36 | // Fetching product data from database 37 | let { data: products, refetch } = useQuery("productsCache", async () => { 38 | const config = { 39 | method: "GET", 40 | headers: { 41 | Authorization: "Basic " + localStorage.token, 42 | }, 43 | }; 44 | const response = await api.get("/products", config); 45 | return response.data; 46 | }); 47 | 48 | const addProduct = () => { 49 | history.push("/add-product"); 50 | }; 51 | 52 | const handleEdit = (id) => { 53 | history.push("/edit-product/" + id); 54 | }; 55 | 56 | // For get id product & show modal confirm delete data 57 | const handleDelete = (id) => { 58 | setIdDelete(id); 59 | handleShow(); 60 | }; 61 | 62 | // If confirm is true, execute delete data 63 | const deleteById = useMutation(async (id) => { 64 | try { 65 | const config = { 66 | method: "DELETE", 67 | headers: { 68 | Authorization: "Basic " + localStorage.token, 69 | }, 70 | }; 71 | await api.delete(`/product/${id}`, config); 72 | refetch(); 73 | } catch (error) { 74 | console.log(error); 75 | } 76 | }); 77 | 78 | useEffect(() => { 79 | if (confirmDelete) { 80 | // Close modal confirm delete data 81 | handleClose(); 82 | // execute delete data by id function 83 | deleteById.mutate(idDelete); 84 | setConfirmDelete(null); 85 | } 86 | }, [confirmDelete]); 87 | 88 | return ( 89 | <> 90 | 91 | 92 | 93 | 94 | 95 |
List Product
96 | 97 | 98 | 105 | 106 | 107 | {products?.length != 0 ? ( 108 | 109 | 110 | 111 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | {products?.map((item, index) => ( 124 | 125 | 126 | 136 | 137 | 151 | 154 | 155 | 175 | 176 | ))} 177 | 178 |
112 | No 113 | PhotoProduct NameProduct DescPriceQtyAction
{index + 1} 127 | 135 | {item.name} 138 | 148 | {item.desc} 149 | 150 | 152 | {rupiahFormat.convert(item.price)} 153 | {item.qty} 156 | 165 | 174 |
179 | ) : ( 180 |
181 | 186 |
No data product
187 |
188 | )} 189 | 190 |
191 |
192 | 197 | 198 | ); 199 | } 200 | -------------------------------------------------------------------------------- /src/pages/UpdateProductAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, createElement } from "react"; 2 | import { Container, Row, Col, Button } from "react-bootstrap"; 3 | import { useParams, useHistory } from "react-router"; 4 | 5 | import NavbarAdmin from "../components/NavbarAdmin"; 6 | import CheckBox from "../components/form/CheckBox"; 7 | 8 | import dataProduct from "../fakeData/product"; 9 | 10 | // Import useQuery and useMutation 11 | import { useQuery, useMutation } from "react-query"; 12 | 13 | // Get API config 14 | import { API } from "../config/api"; 15 | 16 | export default function UpdateProductAdmin() { 17 | const title = "Product admin"; 18 | document.title = "DumbMerch | " + title; 19 | 20 | let history = useHistory(); 21 | let api = API(); 22 | const { id } = useParams(); 23 | 24 | const [categories, setCategories] = useState([]); //Store all category data 25 | const [categoryId, setCategoryId] = useState([]); //Save the selected category id 26 | const [preview, setPreview] = useState(null); //For image preview 27 | const [product, setProduct] = useState({}); //Store product data 28 | const [form, setForm] = useState({ 29 | image: "", 30 | name: "", 31 | desc: "", 32 | price: "", 33 | qty: "", 34 | }); //Store product data 35 | 36 | // Fetching detail product data by id from database 37 | let { productRefetch } = useQuery("productCache", async () => { 38 | const config = { 39 | headers: { 40 | Authorization: "Basic " + localStorage.token, 41 | }, 42 | }; 43 | const response = await api.get("/product/" + id, config); 44 | setForm({ 45 | name: response.data.name, 46 | desc: response.data.desc, 47 | price: response.data.price, 48 | qty: response.data.qty, 49 | image: response.data.image, 50 | }); 51 | setProduct(response.data); 52 | }); 53 | 54 | // Fetching category data 55 | let { categoriesRefetch } = useQuery("categoriesCache", async () => { 56 | const response = await api.get("/categories"); 57 | setCategories(response.data); 58 | }); 59 | 60 | // For handle if category selected 61 | const handleChangeCategoryId = (e) => { 62 | const id = e.target.value; 63 | const checked = e.target.checked; 64 | 65 | if (checked == true) { 66 | // Save category id if checked 67 | setCategoryId([...categoryId, parseInt(id)]); 68 | } else { 69 | // Delete category id from variable if unchecked 70 | let newCategoryId = categoryId.filter((categoryIdItem) => { 71 | return categoryIdItem != id; 72 | }); 73 | setCategoryId(newCategoryId); 74 | } 75 | }; 76 | 77 | // Handle change data on form 78 | const handleChange = (e) => { 79 | setForm({ 80 | ...form, 81 | [e.target.name]: 82 | e.target.type === "file" ? e.target.files : e.target.value, 83 | }); 84 | 85 | // Create image url for preview 86 | if (e.target.type === "file") { 87 | setPreview(e.target.files); 88 | } 89 | }; 90 | 91 | const handleSubmit = useMutation(async (e) => { 92 | try { 93 | e.preventDefault(); 94 | 95 | // Store data with FormData as object 96 | const formData = new FormData(); 97 | if (preview) { 98 | formData.set("image", preview[0], preview[0]?.name); 99 | } 100 | formData.set("name", form.name); 101 | formData.set("desc", form.desc); 102 | formData.set("price", form.price); 103 | formData.set("qty", form.qty); 104 | formData.set("categoryId", categoryId); 105 | 106 | // Configuration 107 | const config = { 108 | method: "PATCH", 109 | headers: { 110 | Authorization: "Basic " + localStorage.token, 111 | }, 112 | body: formData, 113 | }; 114 | 115 | // Insert product data 116 | const response = await api.patch("/product/" + product.id, config); 117 | 118 | history.push("/product-admin"); 119 | } catch (error) { 120 | console.log(error); 121 | } 122 | }); 123 | 124 | useEffect(() => { 125 | const newCategoryId = product?.categories?.map((item) => { 126 | return item.id; 127 | }); 128 | 129 | setCategoryId(newCategoryId); 130 | }, [product]); 131 | 132 | return ( 133 | <> 134 | 135 | 136 | 137 | 138 |
Update Product
139 | 140 | 141 |
handleSubmit.mutate(e)}> 142 | {!preview ? ( 143 |
144 | 152 |
153 | ) : ( 154 |
155 | 163 |
164 | )} 165 | 172 | 175 | 183 | 191 | 199 | 207 | 208 |
209 |
213 | Category 214 |
215 | {product && 216 | categories.map((item) => ( 217 | 225 | ))} 226 |
227 | 228 |
229 | 232 |
233 |
234 | 235 |
236 |
237 | 238 | ); 239 | } 240 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #0b0b0b !important; 3 | color: #ffffff !important; 4 | } 5 | 6 | /* SCROLLBAR */ 7 | /* width */ 8 | ::-webkit-scrollbar { 9 | width: 5px; 10 | } 11 | 12 | /* Track */ 13 | ::-webkit-scrollbar-track { 14 | background: #f1f1f100; 15 | } 16 | 17 | /* Handle */ 18 | ::-webkit-scrollbar-thumb { 19 | background: #888; 20 | } 21 | 22 | /* Handle on hover */ 23 | ::-webkit-scrollbar-thumb:hover { 24 | background: #555; 25 | } 26 | 27 | /* END SCROLLBAR */ 28 | 29 | .bg-black { 30 | background-color: #0b0b0b; 31 | color: #ffffff; 32 | } 33 | 34 | .text-auth-header { 35 | font-style: normal; 36 | font-weight: 400; 37 | font-size: 56px; 38 | line-height: 76px; 39 | } 40 | 41 | .text-auth-parag { 42 | font-family: Avenir; 43 | font-style: normal; 44 | font-weight: normal; 45 | font-size: 18px; 46 | line-height: 25px; 47 | 48 | color: #6a6a6a; 49 | } 50 | 51 | .btn-login { 52 | background: #f74d4d !important; 53 | border-radius: 5px !important; 54 | 55 | font-style: normal !important; 56 | font-weight: 700 !important; 57 | font-size: 18px !important; 58 | line-height: 25px !important; 59 | 60 | color: #ffffff !important; 61 | } 62 | 63 | .btn-login:hover { 64 | background: #d11111 !important; 65 | } 66 | 67 | .btn-login:active { 68 | background: #861313 !important; 69 | } 70 | 71 | .btn-register { 72 | font-style: normal !important; 73 | font-weight: 800 !important; 74 | font-size: 18px !important; 75 | line-height: 25px !important; 76 | 77 | color: #b7b7b7 !important; 78 | } 79 | 80 | .btn-register:hover { 81 | color: #e6e6e6 !important; 82 | } 83 | 84 | .btn-register:active { 85 | color: #b7b7b7 !important; 86 | } 87 | 88 | input:focus, 89 | button:focus, 90 | textarea:focus { 91 | box-shadow: none !important; 92 | outline: none !important; 93 | } 94 | 95 | .card-auth { 96 | background: #181818; 97 | border-radius: 10px; 98 | max-width: 416px; 99 | } 100 | 101 | .form input { 102 | background: rgba(210, 210, 210, 0.25); 103 | border: 2px solid #bcbcbc; 104 | box-sizing: border-box; 105 | border-radius: 5px; 106 | width: 100%; 107 | color: #ffffff; 108 | } 109 | 110 | .text-navbar { 111 | font-weight: 700 !important; 112 | font-size: 18px !important; 113 | line-height: 25px !important; 114 | color: #ffffff !important; 115 | } 116 | 117 | .text-navbar-active { 118 | font-weight: 700 !important; 119 | font-size: 18px !important; 120 | line-height: 25px !important; 121 | color: #f74d4d !important; 122 | } 123 | 124 | .text-header-product { 125 | font-weight: 700; 126 | font-size: 24px; 127 | line-height: 33px; 128 | color: #f74d4d; 129 | } 130 | 131 | .card-product { 132 | background: #212121; 133 | border-radius: 5px; 134 | } 135 | 136 | .card-product:hover { 137 | box-shadow: 0px 0px 10px #3d3d3d; 138 | } 139 | 140 | .text-header-product-item { 141 | font-weight: 600; 142 | font-size: 18px; 143 | line-height: 25px; 144 | color: #f74d4d; 145 | } 146 | 147 | .text-product-item { 148 | font-style: normal; 149 | font-weight: normal; 150 | font-size: 14px; 151 | line-height: 19px; 152 | color: #ffffff; 153 | margin-top: 5px; 154 | } 155 | 156 | .img-rounded { 157 | border-radius: 5px 5px 0 0; 158 | } 159 | 160 | /* Masonry */ 161 | .my-masonry-grid { 162 | display: -webkit-box; /* Not needed if autoprefixing */ 163 | display: -ms-flexbox; /* Not needed if autoprefixing */ 164 | display: flex; 165 | margin-left: -10px; /* gutter size offset */ 166 | width: auto; 167 | } 168 | .my-masonry-grid_column { 169 | padding-left: 10px; /* gutter size */ 170 | background-clip: padding-box; 171 | } 172 | 173 | .my-masonry-grid_column > div { 174 | /* change div to reference your elements you put in */ 175 | /* background: grey; */ 176 | margin-bottom: 30px; 177 | } 178 | 179 | .text-header-product-detail { 180 | font-style: normal; 181 | font-weight: 500; 182 | font-size: 48px; 183 | line-height: 66px; 184 | color: #f74d4d; 185 | } 186 | 187 | .text-content-product-detail { 188 | font-style: normal; 189 | font-weight: normal; 190 | font-size: 18px; 191 | line-height: 25px; 192 | color: #ffffff; 193 | } 194 | 195 | .text-price-product-detail { 196 | font-style: normal; 197 | font-weight: 500; 198 | font-size: 24px; 199 | line-height: 33px; 200 | color: #f74d4d; 201 | } 202 | 203 | .btn-buy { 204 | background: #f74d4d !important; 205 | border-radius: 5px !important; 206 | font-style: normal !important; 207 | font-weight: 500 !important; 208 | font-size: 18px !important; 209 | line-height: 22px !important; 210 | color: #ffffff !important; 211 | } 212 | 213 | .btn-buy:hover { 214 | background: #ce3030 !important; 215 | } 216 | 217 | .btn-buy:active { 218 | background: #701919 !important; 219 | } 220 | 221 | .img-contact { 222 | width: 50px !important; 223 | height: 50px !important; 224 | object-fit: cover !important; 225 | } 226 | 227 | .img-chat { 228 | width: 40px !important; 229 | height: 40px !important; 230 | object-fit: cover !important; 231 | } 232 | 233 | .text-contact { 234 | font-size: 14px; 235 | } 236 | 237 | .text-contact-chat { 238 | color: #ababab; 239 | } 240 | 241 | .contact { 242 | display: flex; 243 | align-items: center; 244 | } 245 | .contact img { 246 | flex-grow: 0; 247 | flex-shrink: 0; 248 | } 249 | 250 | .contact ul { 251 | list-style-type: none; 252 | } 253 | 254 | .contact:hover { 255 | background-color: #212121; 256 | transition: 0.5s; 257 | border-radius: 5px; 258 | cursor: pointer; 259 | } 260 | 261 | .contact:active { 262 | background-color: #3d3d3d; 263 | transition: 0.3s; 264 | } 265 | 266 | .contact-active { 267 | background-color: #212121; 268 | border-radius: 5px; 269 | } 270 | 271 | .input-message { 272 | width: 100%; 273 | height: 90%; 274 | background: rgba(171, 171, 171, 0.15); 275 | border: none; 276 | color: #ffffff; 277 | } 278 | 279 | .chat-me { 280 | position: relative; 281 | background: #262626; 282 | border-radius: 5px; 283 | padding: 10px; 284 | font-weight: 500; 285 | font-size: 16px; 286 | line-height: 22px; 287 | max-width: 400px; 288 | } 289 | 290 | .chat-me::before { 291 | content: ""; 292 | position: absolute; 293 | top: 8px; 294 | right: -15px; 295 | border-top: 15px solid transparent; 296 | border-left: 20px solid #262626; 297 | border-bottom: 15px solid transparent; 298 | } 299 | .chat-other { 300 | position: relative; 301 | background: #575757; 302 | border-radius: 5px; 303 | margin-left: 10px; 304 | padding: 10px; 305 | font-weight: 500; 306 | font-size: 16px; 307 | line-height: 22px; 308 | max-width: 400px; 309 | } 310 | 311 | .chat-other::before { 312 | content: ""; 313 | position: absolute; 314 | top: 8px; 315 | left: -15px; 316 | border-top: 15px solid transparent; 317 | border-right: 20px solid #575757; 318 | border-bottom: 15px solid transparent; 319 | } 320 | .profile-header { 321 | font-style: normal; 322 | font-weight: 600; 323 | font-size: 18px; 324 | line-height: 25px; 325 | color: #f74d4d; 326 | margin-bottom: 6px; 327 | } 328 | 329 | .profile-content { 330 | font-style: normal; 331 | font-weight: normal; 332 | font-size: 18px; 333 | line-height: 25px; 334 | color: #ffffff; 335 | margin-bottom: 27px; 336 | } 337 | 338 | .text-header-category { 339 | font-style: normal; 340 | font-weight: 600; 341 | font-size: 24px; 342 | line-height: 33px; 343 | color: #ffffff; 344 | } 345 | 346 | .input-edit-category { 347 | width: 100%; 348 | background: rgba(210, 210, 210, 0.25); 349 | border: 2px solid #bcbcbc; 350 | box-sizing: border-box; 351 | border-radius: 5px; 352 | padding: 5px 10px; 353 | color: #ffffff; 354 | } 355 | 356 | .label-file-add-product { 357 | background: #f74d4d; 358 | color: white; 359 | padding: 0.5rem 1rem; 360 | font-family: sans-serif; 361 | border-radius: 0.3rem; 362 | cursor: pointer; 363 | margin-top: 1rem; 364 | } 365 | 366 | .card-form-input { 367 | background: rgba(210, 210, 210, 0.25); 368 | border: 2px solid #bcbcbc; 369 | box-sizing: border-box; 370 | border-radius: 5px; 371 | } 372 | 373 | .status-transaction-success { 374 | background-color: #268d37b0; 375 | } 376 | 377 | .status-transaction-pending { 378 | background-color: #8b8d265b; 379 | } 380 | 381 | .status-transaction-failed { 382 | background-color: #8d26265b; 383 | } 384 | -------------------------------------------------------------------------------- /src/assets/empty.svg: -------------------------------------------------------------------------------- 1 | empty --------------------------------------------------------------------------------