useSWR, refreshToken axios intercept, update readme, fixing typo
parent
cecfe95901
commit
78fb71ee9b
@ -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
|
||||
})
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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…
Reference in New Issue