useSWR, refreshToken axios intercept, update readme, fixing typo

dev
Aji Kamaludin 3 years ago
parent cecfe95901
commit 78fb71ee9b
No known key found for this signature in database
GPG Key ID: 670E1F26AD5A8099

@ -12,6 +12,7 @@ contoh react SPA POS ( point of sales ) built with react
- pembelian
- penjualan
- diskon penjualan
- `UI : ChakraUI (https://chakra-ui.com/)`
### start
- install

21
package-lock.json generated

@ -29,6 +29,7 @@
"react": "^17.0.2",
"react-datepicker": "^4.2.0",
"react-dom": "^17.0.2",
"react-number-format": "^4.7.3",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"swr": "^0.5.6",
@ -17986,6 +17987,18 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-number-format": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.7.3.tgz",
"integrity": "sha512-4EvcANjstypQ5anhanmdEioGc49qbnErfS+yqbhatC0vzQ1okplkWNb0DIY7ABu4RhaxzttEz6pypEy8KsqgBQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^0.14 || ^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0",
"react-dom": "^0.14 || ^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0"
}
},
"node_modules/react-onclickoutside": {
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.11.2.tgz",
@ -37117,6 +37130,14 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"react-number-format": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.7.3.tgz",
"integrity": "sha512-4EvcANjstypQ5anhanmdEioGc49qbnErfS+yqbhatC0vzQ1okplkWNb0DIY7ABu4RhaxzttEz6pypEy8KsqgBQ==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-onclickoutside": {
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.11.2.tgz",

@ -25,6 +25,7 @@
"react": "^17.0.2",
"react-datepicker": "^4.2.0",
"react-dom": "^17.0.2",
"react-number-format": "^4.7.3",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.3",
"swr": "^0.5.6",

@ -16,9 +16,11 @@ import "./axiosSetup"
import { library } from '@fortawesome/fontawesome-svg-core'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { AppProvider } from "./context/AppContext"
import ErrorBoundary from "./context/ErrorBoundary";
import NotFound from "./views/errors/404"
import Loading from "./components/Common/Loading"
import AppCrash from "./views/errors/500";
const Login = React.lazy(() => import('./views/auth/Login'))
const Register = React.lazy(() => import('./views/auth/Register'))
@ -41,14 +43,17 @@ class App extends React.Component {
<AppProvider>
<BrowserRouter>
<ChakraProvider theme={customTheme}>
<ErrorBoundary>
<React.Suspense fallback={<Loading/>}>
<Switch>
<Route path="/login" exect={true} render={(props) => <Login {...props} />}/>
<Route path="/register" exect={true} render={(props) => <Register {...props} />}/>
<Route path="/error" exect={true} render={(props) => <AppCrash {...props} />}/>
<Route path="/" render={(props) => <Dashboard {...props} />}/>
<Route path="*" render={(props) => <NotFound/>}/>
</Switch>
</React.Suspense>
</ErrorBoundary>
</ChakraProvider>
</BrowserRouter>
</AppProvider>

@ -1,36 +1,36 @@
export const navs = [
{
name: "Dashboard",
name: "dashboard",
to: "/dashboard",
icon: "clipboard-list",
role: "admin",
},
{
name: "Kasir",
name: "kasir",
to: "/sales/create",
icon: "cash-register",
role: "kasir",
},
{
name: "Penjualan",
name: "penjualan",
to: "/sales",
icon: "money-bill-wave",
role: "admin",
},
{
name: "Pembelian",
name: "pembelian",
to: "/purchases",
icon: "money-bill-wave",
role: "admin",
},
{
name: "Kategori",
name: "kategori",
to: "/categories",
icon: "list",
role: "admin",
},
{
name: "Produk",
name: "produk",
to: "/products",
icon: "list",
role: "admin",

@ -0,0 +1,31 @@
import axios from "axios"
import useSWR from 'swr'
import "../axiosSetup"
import { useAuth } from "../context/AppContext"
import { formatDate } from "../utils"
const fetcher = (url, token) => axios({
method: "GET",
url: url,
headers: {
'Authorization': `Bearer ${token}`
}
}).then(res => {
return res.data.data
})
export function useProducts({startDate, endDate}) {
const { user } = useAuth()
const { data, error } = useSWR([
`/products?startDate=${formatDate(startDate)}&endDate=${formatDate(endDate)}`, user.accessToken
], fetcher)
return [
data,
error,
]
}
export function useProduct(id) {
}

@ -1,19 +1,50 @@
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
const axiosApiInstance = axios.create();
const refreshAccessToken = (refreshToken) => {
return axios({
method: "PUT",
url: '/authentications',
data: { refreshToken }
}).then(res => res.data)
}
axios.interceptors.response.use(
(response) => {
return response
},
async (error) => {
const { status, data: { message } } = error.response
if (status === 401 && message === 'Unauthenticated.') {
window.localStorage.clear()
window.location.reload()
return
}
if(status === 401 && message === "Token maximum age exceeded") {
const originalRequest = error.config;
originalRequest._retry = true;
const user = JSON.parse(window.localStorage.getItem('KASIRAJA_USER'))
const res = await refreshAccessToken(user.refreshToken);
window.localStorage.setItem(
'KASIRAJA_USER',
JSON.stringify({
...user,
accessToken: res.data.accessToken
})
)
axios.defaults.headers.common['Authorization'] = `Bearer ${res.data.accessToken}`;
return axiosApiInstance(originalRequest);
}
if (status === 403) {
window.alert('Anda tidak mempunyai akses untuk aksi ini')
}
throw error
})

@ -9,12 +9,24 @@ import DatePicker from "react-datepicker"
import { useState } from "react"
import { formatDate } from "../../utils"
export default function DatePickerFilter() {
export function useDatePickerFilter() {
const date = new Date()
const [startDate, setStartDate] = useState(date)
const [startDate, setStartDate] = useState(new Date())
const [endDate, setEndDate] = useState(new Date(date.setTime(date.getTime() + (7 * 24 * 60 * 60 * 1000))))
return [
startDate,
endDate,
{
setStartDate,
setEndDate
}
]
}
export function DatePickerFilter({ startDate, endDate, setter : { setStartDate, setEndDate } }) {
const [startDateOpen, setStartDateOpen] = useState(false)
const [endDateOpen, setEndDateOpen] = useState(false)
@ -31,7 +43,10 @@ export default function DatePickerFilter() {
bg="gray.200"
readOnly={true}
focusBorderColor="red.500"
onClick={() => setStartDateOpen(!startDateOpen)}
onClick={() => {
setStartDateOpen(!startDateOpen)
setEndDateOpen(false)
}}
/>
<InputRightElement children={<FontAwesomeIcon icon="calendar-alt" />} />
</InputGroup>
@ -64,7 +79,10 @@ export default function DatePickerFilter() {
bg="gray.200"
readOnly={true}
focusBorderColor="red.500"
onClick={() => setEndDateOpen(!endDateOpen)}
onClick={() => {
setEndDateOpen(!endDateOpen)
setStartDateOpen(false)
}}
/>
<InputRightElement children={<FontAwesomeIcon icon="calendar-alt" />} />
</InputGroup>
@ -77,7 +95,7 @@ export default function DatePickerFilter() {
onChange={(date) => {
setEndDate(date)
setEndDateOpen(!endDateOpen)}
}
}
inline
/>
</PopoverBody>

@ -1,5 +1,10 @@
import { Flex } from '@chakra-ui/react'
import { CircularProgress } from "@chakra-ui/progress"
export default function Loading() {
return <CircularProgress isIndeterminate color="red.300" />
return (
<Flex justifyContent="center" alignItems="center" mt="24" mb="24">
<CircularProgress isIndeterminate color="red.500" />
</Flex>
)
}

@ -33,7 +33,7 @@ const Header = ({ showSidebarButton = true, onShowSidebar, onLogout, user }) =>
</MenuButton>
<MenuList>
<MenuItem color="blackAlpha.900">{user?.name}</MenuItem>
<MenuItem color="blackAlpha.900" onClick={e => onLogout(e)}>Logout</MenuItem>
<MenuItem color="blackAlpha.900" onClick={e => onLogout(e)}>logout</MenuItem>
</MenuList>
</Menu>

@ -0,0 +1,30 @@
import React from 'react'
import AppCrash from '../views/errors/500';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
// logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <AppCrash/>;
}
return this.props.children;
}
}
export default ErrorBoundary

@ -1,3 +1,7 @@
export const formatDate = (date) => {
return date.toLocaleDateString()
}
}
export function formatIDR(amount) {
const idFormatter = new Intl.NumberFormat('id-ID')
return idFormatter.format(amount)
}

@ -53,12 +53,12 @@ export default function Login(props) {
<Box style={{minHeight: "100vh"}} bg="gray.200" alignItems="center">
<Flex py={{ base: 1, lg: 22 }} px={{ base: 1, lg: 12, xl: 52 }} flexFlow={{base: "column", lg: "row" }} justifyContent="center" style={{ minHeight: "100vh", placeItems: "center", gap: "1rem", alignItems: "center"}}>
<Box maxW={{base: "80", lg: "container.lg"}} display={{base: "none", lg: "block"}}>
<Heading pb="7">Hai, kasirAja</Heading>
<Heading pb="7">hai, kasirAja</Heading>
<Text>
kasirAja sebuah sistem POS simple, mudah, cepat, dan modern
</Text>
<Text>
Sistem penjualan dan pembelian yang simple dengan pengelolan produk multi user. modern dengan dibangun diatas rest api dengan menggunakan nodejs, dapat diakses melalui web maupun perangkat mobile dengan aplikasi yang tersedia dan support dengan PWA.
sistem penjualan dan pembelian yang simple dengan pengelolan produk multi user. modern dengan dibangun diatas rest api dengan menggunakan nodejs, dapat diakses melalui web maupun perangkat mobile dengan aplikasi yang tersedia dan support dengan PWA.
</Text>
</Box>
<Box flexShrink="0" shadow="lg" p="8" maxW="96" w="full" bg="white" rounded="lg">
@ -70,7 +70,7 @@ export default function Login(props) {
</Alert>
)}
<FormControl id="email" pb="2">
<FormLabel mb="1">Email</FormLabel>
<FormLabel mb="1">email</FormLabel>
<Input
focusBorderColor="red.500"
type="email"
@ -82,7 +82,7 @@ export default function Login(props) {
/>
</FormControl>
<FormControl id="password" pb="4">
<FormLabel mb="1">Password</FormLabel>
<FormLabel mb="1">password</FormLabel>
<Input
focusBorderColor="red.500"
type="password"
@ -93,7 +93,7 @@ export default function Login(props) {
</FormControl>
<Box mt={5} mb="1" ml="1" fontSize="sm">
<Link to="/register">
Ingin mencoba, daftar ?
ingin mencoba, daftar ?
</Link>
</Box>
<Button
@ -103,7 +103,7 @@ export default function Login(props) {
disabled={submit}
onClick={(e) => handleSubmit(e)}
>
Login
login
</Button>
</Box>
</Box>

@ -67,12 +67,12 @@ export default function RegisterPage(props) {
<Box style={{minHeight: "100vh"}} bg="gray.200" alignItems="center">
<Flex py={{ base: 1, lg: 22 }} px={{ base: 1, lg: 12, xl: 52 }} flexFlow={{base: "column", lg: "row" }} justifyContent="center" style={{ minHeight: "100vh", placeItems: "center", gap: "1rem", alignItems: "center"}}>
<Box maxW={{base: "80", lg: "container.lg"}} display={{base: "none", lg: "block"}}>
<Heading pb="7">Hai, kasirAja</Heading>
<Heading pb="7">hai, kasirAja</Heading>
<Text>
kasirAja sebuah sistem POS simple, mudah, cepat, dan modern
</Text>
<Text>
Sistem penjualan dan pembelian yang simple dengan pengelolan produk multi user. modern dengan dibangun diatas rest api dengan menggunakan nodejs, dapat diakses melalui web maupun perangkat mobile dengan aplikasi yang tersedia dan support dengan PWA.
sistem penjualan dan pembelian yang simple dengan pengelolan produk multi user. modern dengan dibangun diatas rest api dengan menggunakan nodejs, dapat diakses melalui web maupun perangkat mobile dengan aplikasi yang tersedia dan support dengan PWA.
</Text>
</Box>
<Box flexShrink="0" shadow="lg" p="8" maxW="96" w="full" bg="white" rounded="lg">
@ -84,7 +84,7 @@ export default function RegisterPage(props) {
</Alert>
)}
<FormControl id="name" pb="2">
<FormLabel mb="1">Nama Toko</FormLabel>
<FormLabel mb="1">nama toko</FormLabel>
<Input
focusBorderColor="red.500"
type="text"
@ -94,7 +94,7 @@ export default function RegisterPage(props) {
/>
</FormControl>
<FormControl id="email" pb="2">
<FormLabel mb="1">Email</FormLabel>
<FormLabel mb="1">email</FormLabel>
<Input
focusBorderColor="red.500"
type="email"
@ -104,7 +104,7 @@ export default function RegisterPage(props) {
/>
</FormControl>
<FormControl id="password" pb="4">
<FormLabel mb="1">Password</FormLabel>
<FormLabel mb="1">password</FormLabel>
<InputGroup size="md">
<Input
pr="4.5rem"
@ -124,7 +124,7 @@ export default function RegisterPage(props) {
</FormControl>
<Box mt={5} mb="1" ml="1" fontSize="sm">
<Link to="/login">
Sudah punya akun, login ?
sudah punya akun, login ?
</Link>
</Box>
<Button
@ -134,7 +134,7 @@ export default function RegisterPage(props) {
disabled={submit}
onClick={e => handleSubmit(e)}
>
Daftar
daftar
</Button>
</Box>
</Box>

@ -9,7 +9,7 @@ export default function Dashboard() {
<Flex flexShrink="revert" direction="row" justifyContent="flex-start">
<Card>
<Stat>
<StatLabel>Penjualan</StatLabel>
<StatLabel>penjualan</StatLabel>
<StatNumber>345.670</StatNumber>
<StatHelpText>
<StatArrow type="increase" />
@ -19,7 +19,7 @@ export default function Dashboard() {
</Card>
<Card>
<Stat>
<StatLabel>Pembelian</StatLabel>
<StatLabel>pembelian</StatLabel>
<StatNumber>145.670</StatNumber>
<StatHelpText>
<StatArrow type="decrease" />

@ -0,0 +1,11 @@
import { Container, Center } from '@chakra-ui/react'
export default function AppCrash(){
return (
<Container minW="full" minH="full">
<Center bg="red.600" minH="40rem" m="10" color="white" fontSize="2.5rem">
be patient, app crash / down / in maintace
</Center>
</Container>
)
}

@ -1,44 +1,72 @@
import {
Button,
Alert,
AlertIcon,
Box,
Table,
Thead,
Tr,
Td,
Th,
Tbody
Tbody,
Heading
} from "@chakra-ui/react"
import { useProducts } from "../../api"
import Card from "../../components/Common/Card"
import DatePickerFilter from "../../components/Common/DatePickerFilter"
import Loading from "../../components/Common/Loading"
import { DatePickerFilter, useDatePickerFilter } from "../../components/Common/DatePickerFilter"
import { formatIDR } from "../../utils"
export default function List({ history }) {
const [ startDate, endDate, setter ] = useDatePickerFilter()
const [ data, error ] = useProducts({startDate, endDate})
const handleItemClick = (id) => {
history.push(`/products/${id}`)
}
if(error) {
return (
<Alert status="error">
<AlertIcon />
{error.message}
</Alert>
)
}
export default function List() {
const arr = [1,2,3,4,5,6,7,8,9,10,11,12]
return (
<Card>
{/* Tombol Create */}
<Button size="md">Tambah</Button>
{/* Filter Tanggal */}
<DatePickerFilter/>
{/* daftar products */}
<Table variant="simple" mt="2" colorScheme="whatsapp">
<Thead>
<Tr>
<Th>To convert</Th>
<Th>into</Th>
<Th isNumeric>multiply by</Th>
</Tr>
</Thead>
<Tbody>
{
arr.map(() => (
<Tr onClick={() => {alert('Hello')}}>
<Td>inches</Td>
<Td>millimetres (mm)</Td>
<Td isNumeric>25.4</Td>
<>
<Box p="3" m="2" bg="white" rounded="lg">
<Heading size="md">dashboard / produk</Heading>
</Box>
<Card>
<Button size="md">tambah</Button>
<DatePickerFilter startDate={startDate} endDate={endDate} setter={setter} />
{data ? (
<Table variant="simple" mt="2">
<Thead>
<Tr>
<Th>nama</Th>
<Th isNumeric>harga beli</Th>
<Th isNumeric>harga jual</Th>
<Th>deskripsi</Th>
</Tr>
))
}
</Tbody>
</Table>
</Card>
</Thead>
<Tbody>
{data.products.map((product) => (
<Tr onClick={() => handleItemClick(product.id)} key={product.id}>
<Td>{ product.name }</Td>
<Td isNumeric>{ formatIDR(product.cost) }</Td>
<Td isNumeric>{ formatIDR(product.price) }</Td>
<Td>{ product.description }</Td>
</Tr>
))}
</Tbody>
</Table>
) : (
<Loading/>
)}
</Card>
</>
)
}
Loading…
Cancel
Save