├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── assets │ ├── images │ │ ├── logo.png │ │ ├── bg-cart.webp │ │ ├── bg-profile.webp │ │ ├── cold-brew.webp │ │ └── bg-main-coffee.webp │ └── screenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png ├── manifest.json └── index.html ├── src ├── assets │ ├── images │ │ ├── product-1.webp │ │ ├── team-work.webp │ │ ├── placeholder-promo.jpg │ │ ├── placeholder-image.webp │ │ ├── placeholder-profile.jpg │ │ ├── person-with-a-coffee.webp │ │ ├── loading.svg │ │ ├── partners │ │ │ ├── netflix.svg │ │ │ ├── discord.svg │ │ │ ├── amazon.svg │ │ │ ├── reddit.svg │ │ │ └── spotify.svg │ │ ├── not_found.svg │ │ └── empty-box.svg │ ├── illustrations │ │ └── mobile-search-undraw.png │ ├── icons │ │ ├── burger-menu-left.svg │ │ ├── facebook.svg │ │ ├── check-circle.svg │ │ ├── icon-pen.svg │ │ ├── love.svg │ │ ├── star.svg │ │ ├── close.svg │ │ ├── user.svg │ │ ├── check.svg │ │ ├── place.svg │ │ ├── twitter.svg │ │ ├── chat.svg │ │ └── instagram.svg │ └── logo.svg ├── pages │ ├── Promo │ │ └── index.jsx │ ├── History │ │ ├── HistoryDetail.jsx │ │ └── index.jsx │ ├── Profile │ │ ├── DeletePhotoModal.jsx │ │ └── EditPassword.jsx │ ├── Error │ │ └── index.jsx │ ├── Auth │ │ ├── index.jsx │ │ ├── ForgotPass.jsx │ │ ├── ResetPass.jsx │ │ ├── Register.jsx │ │ └── Login.jsx │ ├── Cart │ │ └── testpersist.jsx │ └── Products │ │ ├── NewProduct.jsx │ │ ├── EditProduct.jsx │ │ └── GetAllProducts.jsx ├── utils │ ├── dataProvider │ │ ├── base │ │ │ └── index.js │ │ ├── admin.js │ │ ├── userPanel.js │ │ ├── profile.js │ │ ├── auth.js │ │ ├── transaction.js │ │ ├── promo.js │ │ └── products.js │ ├── localStorage.js │ ├── scrollToTop.js │ ├── documentTitle.js │ ├── wrappers │ │ ├── withSearchParams.js │ │ └── protectedRoute.js │ ├── authUtils.js │ └── helpers.js ├── setupTests.js ├── tests │ └── App.test.js ├── components │ ├── Loading.jsx │ ├── Promo │ │ ├── PromoNotFound.jsx │ │ └── DeletePromo.jsx │ ├── Button.jsx │ ├── Product │ │ ├── ProductNotFound.jsx │ │ └── DeleteProduct.jsx │ ├── Modal.jsx │ ├── Logout.jsx │ ├── Notification.jsx │ ├── AuthFooter.jsx │ ├── Footer.jsx │ └── Sidebar.jsx ├── reportWebVitals.js ├── redux │ ├── slices │ │ ├── index.js │ │ ├── context.slice.js │ │ ├── userInfo.slice.js │ │ ├── price.slice.js │ │ ├── profile.slice.js │ │ └── cart.slice.js │ └── store.js ├── styles │ └── index.css ├── index.js └── router.js ├── .gitignore ├── .eslintrc.json ├── target └── npmlist.json ├── LICENSE ├── tailwind.config.js ├── package.json └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/images/logo.png -------------------------------------------------------------------------------- /public/assets/screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/screenshots/1.png -------------------------------------------------------------------------------- /public/assets/screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/screenshots/2.png -------------------------------------------------------------------------------- /public/assets/screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/screenshots/3.png -------------------------------------------------------------------------------- /public/assets/screenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/screenshots/4.png -------------------------------------------------------------------------------- /public/assets/screenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/screenshots/5.png -------------------------------------------------------------------------------- /public/assets/images/bg-cart.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/images/bg-cart.webp -------------------------------------------------------------------------------- /src/assets/images/product-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/images/product-1.webp -------------------------------------------------------------------------------- /src/assets/images/team-work.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/images/team-work.webp -------------------------------------------------------------------------------- /public/assets/images/bg-profile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/images/bg-profile.webp -------------------------------------------------------------------------------- /public/assets/images/cold-brew.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/images/cold-brew.webp -------------------------------------------------------------------------------- /src/assets/images/placeholder-promo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/images/placeholder-promo.jpg -------------------------------------------------------------------------------- /public/assets/images/bg-main-coffee.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/public/assets/images/bg-main-coffee.webp -------------------------------------------------------------------------------- /src/assets/images/placeholder-image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/images/placeholder-image.webp -------------------------------------------------------------------------------- /src/assets/images/placeholder-profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/images/placeholder-profile.jpg -------------------------------------------------------------------------------- /src/assets/images/person-with-a-coffee.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/images/person-with-a-coffee.webp -------------------------------------------------------------------------------- /src/assets/illustrations/mobile-search-undraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k1rana/jokopi-react/HEAD/src/assets/illustrations/mobile-search-undraw.png -------------------------------------------------------------------------------- /src/pages/Promo/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function AllPromo() { 4 | return
AllPromo
; 5 | } 6 | 7 | export default AllPromo; 8 | -------------------------------------------------------------------------------- /src/pages/History/HistoryDetail.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function HistoryDetail() { 4 | return
HistoryDetail
; 5 | } 6 | 7 | export default HistoryDetail; 8 | -------------------------------------------------------------------------------- /src/pages/Profile/DeletePhotoModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function DeletePhotoModal() { 4 | return
DeletePhotoModal
; 5 | } 6 | 7 | export default DeletePhotoModal; 8 | -------------------------------------------------------------------------------- /src/utils/dataProvider/base/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const host = process.env.REACT_APP_BACKEND_HOST; 4 | 5 | const api = axios.create({ 6 | baseURL: host, 7 | }); 8 | 9 | export default api; 10 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/utils/localStorage.js: -------------------------------------------------------------------------------- 1 | export const save = (key, value) => { 2 | localStorage.setItem(key, value); 3 | }; 4 | export const get = (key) => { 5 | return localStorage.getItem(key); 6 | }; 7 | export const remove = (key) => { 8 | localStorage.removeItem(key); 9 | }; 10 | -------------------------------------------------------------------------------- /src/tests/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/utils/scrollToTop.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | import { useLocation } from 'react-router-dom'; 4 | 5 | export default function ScrollToTop() { 6 | const { pathname } = useLocation(); 7 | 8 | useEffect(() => { 9 | window.scrollTo(0, 0); 10 | }, [pathname]); 11 | 12 | return null; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Loading.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import loadingImage from "../assets/images/loading.svg"; 4 | 5 | function Loading() { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | ); 13 | } 14 | 15 | export default Loading; 16 | -------------------------------------------------------------------------------- /src/components/Promo/PromoNotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { connect } from "react-redux"; 4 | 5 | const PromoNotFound = (props) => { 6 | return
PromoNotFound
; 7 | }; 8 | 9 | const mapStateToProps = (state) => ({ 10 | userInfo: state.userInfo, 11 | }); 12 | 13 | const mapDispatchToProps = {}; 14 | 15 | export default connect(mapStateToProps, mapDispatchToProps)(PromoNotFound); 16 | -------------------------------------------------------------------------------- /.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 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /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/redux/slices/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | 3 | import cartSlice from "./cart.slice"; 4 | import contextSlice from "./context.slice"; 5 | import profileSlice from "./profile.slice"; 6 | import uinfoSlice from "./userInfo.slice"; 7 | 8 | const reducers = combineReducers({ 9 | userInfo: uinfoSlice, 10 | profile: profileSlice, 11 | cart: cartSlice, 12 | context: contextSlice, 13 | }); 14 | 15 | export default reducers; 16 | -------------------------------------------------------------------------------- /src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | // Button.jsx 2 | import React from "react"; 3 | 4 | const Button = ({ children, className, ...props }) => { 5 | return ( 6 | 12 | ); 13 | }; 14 | 15 | export default Button; 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "plugin:react/recommended" 8 | ], 9 | "overrides": [ 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "react" 17 | ], 18 | "rules": { 19 | "react/jsx-key":"warn", 20 | "react/prop-types":"off" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/icons/burger-menu-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/documentTitle.js: -------------------------------------------------------------------------------- 1 | // useDocumentTitle.js 2 | import { useEffect, useRef } from "react"; 3 | 4 | function useDocumentTitle(title, prevailOnUnmount = false) { 5 | const defaultTitle = useRef(document.title); 6 | 7 | useEffect(() => { 8 | document.title = `${title} - ${process.env.REACT_APP_WEBSITE_NAME}`; 9 | }, [title]); 10 | 11 | useEffect( 12 | () => () => { 13 | if (!prevailOnUnmount) { 14 | document.title = defaultTitle.current; 15 | } 16 | }, 17 | [] 18 | ); 19 | } 20 | 21 | export default useDocumentTitle; 22 | -------------------------------------------------------------------------------- /src/pages/Error/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { NavLink } from 'react-router-dom'; 4 | 5 | import notfoundImage from '../../assets/images/empty-box.svg'; 6 | 7 | export default function NotFound() { 8 | return ( 9 |
10 | Not Found 11 |

Page Not Found

12 | 13 | Back to Home 14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/wrappers/withSearchParams.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useNavigate, useSearchParams } from "react-router-dom"; 4 | 5 | function withSearchParams(Component) { 6 | function Wrapper(props) { 7 | const [searchParams, setSearchParams] = useSearchParams(); 8 | const navigate = useNavigate(); 9 | return ( 10 | 16 | ); 17 | } 18 | return Wrapper; 19 | } 20 | 21 | export default withSearchParams; 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/dataProvider/admin.js: -------------------------------------------------------------------------------- 1 | import api from "./base"; 2 | 3 | export const getMonthlyReport = (token, controller) => { 4 | return api.get("/apiv1/adminPanel/monthlyReport", { 5 | headers: { 6 | Authorization: `Bearer ${token}`, 7 | }, 8 | signal: controller.signal, 9 | }); 10 | }; 11 | 12 | export const getSellingReport = (view = "monthly", token, controller) => { 13 | return api.get("/apiv1/adminPanel/reports", { 14 | params: { 15 | view, 16 | }, 17 | headers: { 18 | Authorization: `Bearer ${token}`, 19 | }, 20 | signal: controller.signal, 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /src/assets/icons/check-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/icon-pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); 6 | 7 | * { 8 | font-family: "Poppins", sans-serif; 9 | } 10 | @layer base { 11 | input[type="number"]::-webkit-inner-spin-button, 12 | input[type="number"]::-webkit-outer-spin-button { 13 | -webkit-appearance: none; 14 | margin: 0; 15 | } 16 | } 17 | 18 | input[type="number"] { 19 | -moz-appearance: textfield; 20 | } 21 | 22 | .global-px { 23 | @apply mx-auto px-3 max-w-sm sm:max-w-lg md:max-w-3xl lg:max-w-5xl xl:max-w-6xl; 24 | } 25 | -------------------------------------------------------------------------------- /target/npmlist.json: -------------------------------------------------------------------------------- 1 | {"version":"0.1.0","name":"jokopi-react","dependencies":{"@reduxjs/toolkit":{"version":"1.9.3"},"@testing-library/jest-dom":{"version":"5.16.5"},"@testing-library/react":{"version":"13.4.0"},"@testing-library/user-event":{"version":"13.5.0"},"axios":{"version":"1.3.4"},"daisyui":{"version":"2.51.6"},"jwt-decode":{"version":"3.1.2"},"lodash":{"version":"4.17.21"},"react-burger-menu":{"version":"3.0.9"},"react-dom":{"version":"18.2.0"},"react-hot-toast":{"version":"2.4.0"},"react-image-crop":{"version":"10.0.9"},"react-redux":{"version":"8.0.5"},"react-router-dom":{"version":"6.9.0"},"react-scripts":{"version":"5.0.1"},"react":{"version":"18.2.0"},"redux-persist":{"version":"6.0.0"},"redux":{"version":"4.2.1"},"web-vitals":{"version":"2.1.4"}}} -------------------------------------------------------------------------------- /src/assets/images/loading.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /src/redux/slices/context.slice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const contextSlice = createSlice({ 4 | name: "context", 5 | initialState: { 6 | logout: false, 7 | }, 8 | reducers: { 9 | toggleLogout: (prevState, action) => { 10 | return { 11 | ...prevState, 12 | logout: !prevState.logout, 13 | }; 14 | }, 15 | openLogout: (prevState, action) => { 16 | return { 17 | ...prevState, 18 | logout: true, 19 | }; 20 | }, 21 | closeLogout: (prevState, action) => { 22 | return { 23 | ...prevState, 24 | logout: false, 25 | }; 26 | }, 27 | }, 28 | }); 29 | 30 | export const contextAct = contextSlice.actions; 31 | export default contextSlice.reducer; 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2023 Farhan Brillan W alias nyannss 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /src/assets/icons/love.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Product/ProductNotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { NavLink } from "react-router-dom"; 4 | 5 | import lostImage from "../../assets/images/not_found.svg"; 6 | 7 | function ProductNotFound() { 8 | return ( 9 |
10 | 404 11 |
12 |

Product Not Found

13 | 14 | 17 | 18 |
19 |
20 | ); 21 | } 22 | 23 | export default ProductNotFound; 24 | -------------------------------------------------------------------------------- /src/redux/slices/userInfo.slice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const uinfoSlice = createSlice({ 4 | name: `${process.env.REACT_APP_WEBSITE_NAME}_appdata`, 5 | initialState: { 6 | token: "", 7 | newToken: "", 8 | role: "", 9 | }, 10 | reducers: { 11 | assignToken: (prevState, action) => { 12 | return { 13 | ...prevState, 14 | token: action.payload, 15 | }; 16 | }, 17 | assignData: (prevState, action) => { 18 | return { 19 | ...prevState, 20 | role: action.payload.role, 21 | }; 22 | }, 23 | dismissToken: (prevState) => { 24 | return { 25 | ...prevState, 26 | token: "", 27 | role: "", 28 | }; 29 | }, 30 | }, 31 | }); 32 | 33 | export const uinfoAct = uinfoSlice.actions; 34 | export default uinfoSlice.reducer; 35 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | FLUSH, 3 | PAUSE, 4 | PERSIST, 5 | persistReducer, 6 | persistStore, 7 | PURGE, 8 | REGISTER, 9 | REHYDRATE, 10 | } from "redux-persist"; 11 | import storage from "redux-persist/lib/storage"; // defaults to localStorage for web 12 | 13 | import { configureStore } from "@reduxjs/toolkit"; 14 | 15 | import reducer from "./slices"; 16 | 17 | const persistConfig = { 18 | key: "jokopi_appdata", 19 | storage, 20 | blacklist: ["context"], 21 | }; 22 | 23 | const persistedReducer = persistReducer(persistConfig, reducer); 24 | 25 | const store = configureStore({ 26 | reducer: persistedReducer, 27 | middleware: (defaultMiddleware) => { 28 | return defaultMiddleware({ 29 | serializableCheck: { 30 | ignoreActions: [PERSIST, FLUSH, REHYDRATE, PAUSE, REGISTER, PURGE], 31 | }, 32 | }); 33 | }, 34 | }); 35 | export const persistor = persistStore(store); 36 | export default store; 37 | -------------------------------------------------------------------------------- /src/assets/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/authUtils.js: -------------------------------------------------------------------------------- 1 | import jwt_decode from "jwt-decode"; 2 | 3 | import { uinfoAct } from "../redux/slices/userInfo.slice"; 4 | import store from "../redux/store"; 5 | 6 | export function uinfoFromRedux() { 7 | const state = store.getState(); 8 | const userInfo = state.userInfo; 9 | return userInfo; 10 | } 11 | 12 | export function isAuthenticated() { 13 | const userInfo = uinfoFromRedux(); 14 | if (userInfo.token.length > 0) { 15 | const decoded = jwt_decode(userInfo.token); 16 | const currentTime = Date.now() / 1000; 17 | 18 | if (decoded.exp < currentTime) { 19 | store.dispatch(uinfoAct.dismissToken()); 20 | return false; 21 | } 22 | 23 | return true; 24 | } else { 25 | return false; 26 | } 27 | } 28 | 29 | export function getUserData() { 30 | const userInfo = uinfoFromRedux(); 31 | if (userInfo.token.length > 0) { 32 | const decoded = jwt_decode(userInfo.token); 33 | return decoded; 34 | } 35 | return {}; 36 | } 37 | -------------------------------------------------------------------------------- /src/assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/place.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/Auth/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Outlet } from 'react-router-dom'; 4 | 5 | import AuthFooter from '../../components/AuthFooter'; 6 | 7 | const Auth = () => { 8 | return ( 9 | <> 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 | ); 27 | }; 28 | 29 | export default Auth; 30 | -------------------------------------------------------------------------------- /src/pages/Cart/testpersist.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import { 4 | useDispatch, 5 | useSelector, 6 | } from 'react-redux'; 7 | 8 | import Footer from '../../components/Footer'; 9 | import Header from '../../components/Header'; 10 | import { uinfoAct } from '../../redux/slices/userInfo.slice'; 11 | 12 | function Cart() { 13 | const token = useSelector((state) => state.userInfo); 14 | const [input, setInput] = useState(token.token); 15 | const dispatch = useDispatch(); 16 | const handlerAct = (e) => { 17 | if (e.key === "Enter") { 18 | dispatch(uinfoAct.assignToken(e.target.value)); 19 | } 20 | }; 21 | // console.log(jwtDecode(get("jokopi-token"))); 22 | return ( 23 | <> 24 |
25 | {token.token} 26 |
27 | setInput(e.target.value)} 31 | onKeyDown={handlerAct} 32 | /> 33 |