diff --git a/.prettierrc b/.prettierrc new file mode 100755 index 0000000..ffc0eab --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/package-lock.json b/package-lock.json index 7605cde..16e87d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "react-chakra", "version": "0.1.0", "dependencies": { "@chakra-ui/icons": "^1.0.15", @@ -23,6 +24,7 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", + "axios": "^0.21.1", "framer-motion": "^4.1.17", "is_js": "^0.9.0", "react": "^17.0.2", @@ -5780,6 +5782,14 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dependencies": { + "follow-redirects": "^1.10.0" + } + }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -27579,6 +27589,14 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.2.tgz", "integrity": "sha512-5LMaDRWm8ZFPAEdzTYmgjjEdj1YnQcpfrVajO/sn/LhbpGp0Y0H64c2hLZI1gRMxfA+w1S71Uc/nHaOXgcCvGg==" }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", diff --git a/package.json b/package.json index d59b304..8a45ece 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "react-chakra", - "version": "0.1.0", + "name": "react-kasiraja-web", + "version": "1.0.0", "private": true, "dependencies": { "@chakra-ui/icons": "^1.0.15", @@ -19,6 +19,7 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.7", "@testing-library/user-event": "^12.8.3", + "axios": "^0.21.1", "framer-motion": "^4.1.17", "is_js": "^0.9.0", "react": "^17.0.2", diff --git a/src/App.js b/src/App.js index 76aa4ae..b620dbf 100644 --- a/src/App.js +++ b/src/App.js @@ -11,10 +11,11 @@ import { } from 'react-router-dom' import "@fontsource/raleway/400.css" import "@fontsource/open-sans/700.css" +import "./axiosSetup" import { library } from '@fortawesome/fontawesome-svg-core' import { fas } from '@fortawesome/free-solid-svg-icons' -import NotFound from "./views/errors/404" import { AppProvider } from "./context/AppContext" +import NotFound from "./views/errors/404" import Loading from "./components/Common/Loading" diff --git a/src/_nav.js b/src/_nav.js index 3cc6873..f708017 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -2,31 +2,37 @@ export const navs = [ { name: "Dashboard", to: "/dashboard", - icon: "clipboard-list" + icon: "clipboard-list", + role: "admin", }, { name: "Kasir", to: "/sales/create", - icon: "cash-register" + icon: "cash-register", + role: "kasir", }, { name: "Penjualan", to: "/sales", - icon: "money-bill-wave" + icon: "money-bill-wave", + role: "admin", }, { name: "Pembelian", to: "/purchases", - icon: "money-bill-wave" + icon: "money-bill-wave", + role: "admin", }, { name: "Kategori", to: "/categories", - icon: "list" + icon: "list", + role: "admin", }, { name: "Produk", to: "/products", - icon: "list" + icon: "list", + role: "admin", }, ] \ No newline at end of file diff --git a/src/axiosSetup.js b/src/axiosSetup.js new file mode 100644 index 0000000..d656f43 --- /dev/null +++ b/src/axiosSetup.js @@ -0,0 +1,19 @@ +import axios from 'axios' +import { API_URL } from './config' + +const id = x => x +axios.defaults.baseURL = API_URL +axios.interceptors.response.use(id, error => { + const { status, data: { message } } = error.response + if (status === 401 && message === 'Unauthenticated.') { + window.localStorage.clear() + window.location.reload() + return + } + // if expired access token lets refresh token + if (status === 403) { + window.alert('Anda tidak mempunyai akses untuk aksi ini') + } + + throw error +}) diff --git a/src/components/Layout/Header.js b/src/components/Layout/Header.js index db53f65..5d96545 100644 --- a/src/components/Layout/Header.js +++ b/src/components/Layout/Header.js @@ -10,7 +10,8 @@ import { } from '@chakra-ui/react' import { ChevronRightIcon } from '@chakra-ui/icons' -const Header = ({ showSidebarButton = true, onShowSidebar, onLogout }) => { +const Header = ({ showSidebarButton = true, onShowSidebar, onLogout, user }) => { + return ( @@ -28,10 +29,10 @@ const Header = ({ showSidebarButton = true, onShowSidebar, onLogout }) => { - + - User: Admin + {user?.name} onLogout(e)}>Logout diff --git a/src/components/Layout/Sidebar.js b/src/components/Layout/Sidebar.js index 3c3818e..b960b13 100644 --- a/src/components/Layout/Sidebar.js +++ b/src/components/Layout/Sidebar.js @@ -13,6 +13,7 @@ import { import { Link } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { navs } from '../../_nav' +import { useAuth } from '../../context/AppContext' const MenuItem = ({ name, icon, onClick }) => { @@ -28,7 +29,9 @@ const MenuItem = ({ name, icon, onClick }) => { ) } -const SidebarContent = ({ onClick, variant }) => ( +const SidebarContent = ({ onClick, variant }) => { + const { user } = useAuth() + return ( {variant === 'sidebar' && ( ( >kasirAja )} {navs.map(nav => ( - + ))} -) + ) +} const Sidebar = ({ isOpen, variant, onClose }) => { return variant === 'sidebar' ? ( diff --git a/src/context/AppContext.js b/src/context/AppContext.js index 80d5145..e18129d 100644 --- a/src/context/AppContext.js +++ b/src/context/AppContext.js @@ -16,7 +16,7 @@ const userManager = { const AppContext = React.createContext() function AppProvider(props) { - const [user, setUser] = useState(userManager.get()) + const [user, setUser] = useState(JSON.parse(userManager.get())) const value = useMemo( () => ({ user, setUser }), @@ -39,7 +39,7 @@ function useAuth() { return is.not.empty(user) && is.not.null(user) } const persistUser = user => { - userManager.set(user) + userManager.set(JSON.stringify(user)) setUser(user) } const logout = () => { @@ -48,6 +48,7 @@ function useAuth() { } return { + user, isLoggedIn, persistUser, logout diff --git a/src/layouts/Dashboard.js b/src/layouts/Dashboard.js index 5ba1603..2937cfd 100644 --- a/src/layouts/Dashboard.js +++ b/src/layouts/Dashboard.js @@ -1,4 +1,4 @@ -import { useState, Suspense } from "react"; +import { useEffect, useState, Suspense } from "react"; import { Switch, Route, Redirect } from 'react-router-dom' import { Container, @@ -19,7 +19,7 @@ const smVariant = { navigation: 'drawer', navigationButton: true } const mdVariant = { navigation: 'sidebar', navigationButton: false } export default function DashboardLayout(props) { - const { loading, isLoggedIn, logout } = useAuth() + const { loading, isLoggedIn, logout, user } = useAuth() const { history } = props; @@ -29,15 +29,18 @@ export default function DashboardLayout(props) { history.push('/login') } - if(!isLoggedIn()){ - history.push('/login'); - } - const [isSidebarOpen, setSidebarOpen] = useState(false) const variants = useBreakpointValue({ base: smVariant, md: mdVariant }) const toggleSidebar = () => setSidebarOpen(!isSidebarOpen) + useEffect(() => { + const { history } = props; + if(!isLoggedIn()){ + history.push('/login'); + } + }) + if(loading) { return } @@ -50,8 +53,9 @@ export default function DashboardLayout(props) { showSidebarButton={variants?.navigationButton} onShowSidebar={toggleSidebar} onLogout={handleLogout} + user={user} /> - + {/* Content */} }> diff --git a/src/views/auth/Api.js b/src/views/auth/Api.js new file mode 100644 index 0000000..1143e26 --- /dev/null +++ b/src/views/auth/Api.js @@ -0,0 +1,19 @@ +import axios from 'axios' + +export function login(payload) { + const { email, password } = payload + return axios({ + method: 'POST', + url: '/authentications', + data: { email, password } + }).then(response => response.data) +} + +export function register(payload) { + const { name, email, password } = payload + return axios({ + method: 'POST', + url: '/registration', + data: { name, email, password } + }).then(res => res.data) +} \ No newline at end of file diff --git a/src/views/auth/Login.js b/src/views/auth/Login.js index fd4fabe..b372cbe 100644 --- a/src/views/auth/Login.js +++ b/src/views/auth/Login.js @@ -7,13 +7,40 @@ import { FormLabel, Input, Button, + Alert, + AlertIcon, } from "@chakra-ui/react" -import { useEffect } from "react" +import { useEffect, useState } from "react" import { Link } from "react-router-dom" import { useAuth } from "../../context/AppContext" +import { login } from './Api' export default function Login(props) { - const { isLoggedIn } = useAuth() + const { isLoggedIn, persistUser } = useAuth() + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const [errors, setErrors] = useState([]) + const [submit, setSubmit] = useState(false); + + const handleSubmit = (e) => { + e.preventDefault() + setSubmit(true) + login({ email, password }) + .then(res => { + const { data } = res + persistUser({ + ...data.user, + accessToken: data.accessToken, + refreshToken: data.refreshToken, + }) + }) + .catch(err => { + setErrors(err.response.data) + }) + .finally(() => setSubmit(false)) + } useEffect(() => { const { history } = props @@ -36,13 +63,33 @@ export default function Login(props) { + {errors.message?.length > 0 && ( + + + {errors.message} + + )} Email - + { + setEmail(e.target.value) + }} + /> Password - + setPassword(e.target.value)} + /> @@ -53,6 +100,8 @@ export default function Login(props) { p={6} w="100%" type="submit" + disabled={submit} + onClick={(e) => handleSubmit(e)} > Login diff --git a/src/views/auth/Register.js b/src/views/auth/Register.js index 3de15f1..cd7366f 100644 --- a/src/views/auth/Register.js +++ b/src/views/auth/Register.js @@ -7,13 +7,54 @@ import { FormLabel, Input, Button, + Alert, + AlertIcon, + InputGroup, + InputRightElement, + useToast, } from "@chakra-ui/react" -import { useEffect } from "react" +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { useEffect, useState } from "react" import { Link } from "react-router-dom" import { useAuth } from "../../context/AppContext" +import { register } from "./Api" -export default function Register(props) { - const { isLoggedIn } = useAuth() +export default function RegisterPage(props) { + const { history } = props + const { isLoggedIn } = useAuth() + + const toast = useToast() + + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + const [errors, setErrors] = useState([]) + const [submit, setSubmit] = useState(false) + + const [show, setShow] = useState(false) + + const handleClick = (e) => { setShow(!show) } + + const handleSubmit = (e) => { + e.preventDefault() + setSubmit(true) + register({ name, email, password }) + .then((res) => { + setErrors([]) + toast({ + title: res.message, + description: "anda dapat menggunakan login sekarang", + type: "info", + position: "top-right" + }) + history.push('/login') + }) + .catch((err) => { + setErrors(err.response.data) + }) + .finally(() => setSubmit(false)) + } useEffect(() => { const { history } = props @@ -36,17 +77,50 @@ export default function Register(props) { + {errors.message?.length > 0 && ( + + + {errors.message} + + )} Nama Toko - + setName(e.target.value)} + /> Email - + setEmail(e.target.value)} + /> Password - + + setPassword(e.target.value)} + /> + + + + + @@ -57,6 +131,8 @@ export default function Register(props) { p={6} w="100%" type="submit" + disabled={submit} + onClick={e => handleSubmit(e)} > Daftar diff --git a/src/views/dashboard/Dashboard.js b/src/views/dashboard/Dashboard.js index 900d7ab..dfc6439 100644 --- a/src/views/dashboard/Dashboard.js +++ b/src/views/dashboard/Dashboard.js @@ -1,12 +1,9 @@ -import { - Button, +import { Flex, Box, - useToast } from "@chakra-ui/react" export default function Dashboard() { - const toast = useToast() return ( @@ -20,20 +17,6 @@ export default function Dashboard() {
Dashboard
-
) } \ No newline at end of file