├── 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 |

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 |

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 |
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 |
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 |
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 |
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 |
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 |
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 | | No |
103 | Category Name |
104 | Action |
105 |
106 |
107 |
108 | {categories?.map((item, index) => (
109 |
110 | |
111 | {index + 1}
112 | |
113 |
114 | {item.name}
115 | |
116 |
117 |
126 |
135 | |
136 |
137 | ))}
138 |
139 |
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 |
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 |
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 |
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 | |
112 | No
113 | |
114 | Photo |
115 | Product Name |
116 | Product Desc |
117 | Price |
118 | Qty |
119 | Action |
120 |
121 |
122 |
123 | {products?.map((item, index) => (
124 |
125 | | {index + 1} |
126 |
127 |
135 | |
136 | {item.name} |
137 |
138 |
148 | {item.desc}
149 |
150 | |
151 |
152 | {rupiahFormat.convert(item.price)}
153 | |
154 | {item.qty} |
155 |
156 |
165 |
174 | |
175 |
176 | ))}
177 |
178 |
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 |
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 |
--------------------------------------------------------------------------------