forked from polka_billy/porridger
@ -1,4 +1,4 @@
|
||||
export { default as useHomeAnnouncementList } from './useHomeAnnouncementList'
|
||||
export { default as useAnnouncements } from './useAnnouncements'
|
||||
export { default as useBook } from './useBook'
|
||||
export { default as useAuth } from './useAuth'
|
||||
export { default as useTrashboxes } from './useTrashboxes'
|
||||
|
18
front/src/hooks/api/useAnnouncements.ts
Normal file
18
front/src/hooks/api/useAnnouncements.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import useFetch from './useFetch'
|
||||
import { FiltersType } from '../../utils/filters'
|
||||
import { composeAnnouncementsURL, initialAnnouncements, processAnnouncements } from '../../api/announcements'
|
||||
|
||||
import { isAnnouncementsResponse } from '../../api/announcements/types'
|
||||
|
||||
const useAnnouncements = (filters: FiltersType) => {
|
||||
return useFetch(
|
||||
composeAnnouncementsURL(filters),
|
||||
'GET',
|
||||
false,
|
||||
processAnnouncements,
|
||||
isAnnouncementsResponse,
|
||||
initialAnnouncements
|
||||
)
|
||||
}
|
||||
|
||||
export default useAnnouncements
|
@ -1,11 +1,53 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { handleHTTPErrors, isAborted } from '../../utils'
|
||||
import { getToken } from '../../utils/auth'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
|
||||
type UseFetchShared = {
|
||||
loading: boolean,
|
||||
abort?: () => void,
|
||||
}
|
||||
|
||||
type UseFetchSucced<T> = {
|
||||
error: null,
|
||||
data: T,
|
||||
} & UseFetchShared
|
||||
|
||||
type UseFetchErrored = {
|
||||
error: string,
|
||||
data: undefined
|
||||
} & UseFetchShared
|
||||
|
||||
const gotError = <T>(res: UseFetchErrored | UseFetchSucced<T>): res is UseFetchErrored => {
|
||||
return typeof res.error === 'string'
|
||||
}
|
||||
|
||||
type UseFetchReturn<T> = ({
|
||||
error: null,
|
||||
data: T
|
||||
} | {
|
||||
error: string,
|
||||
data: undefined
|
||||
}) & {
|
||||
loading: boolean,
|
||||
abort?: (() => void)
|
||||
}
|
||||
|
||||
const useFetch = <R, T>(
|
||||
url: string,
|
||||
method: RequestInit['method'],
|
||||
needAuth: boolean,
|
||||
processData: (data: R) => T,
|
||||
guardResponse: (data: unknown) => data is R,
|
||||
initialData?: T,
|
||||
params?: RequestInit
|
||||
): UseFetchReturn<T> => {
|
||||
const [data, setData] = useState(initialData)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const abortControllerRef = useRef<AbortController>()
|
||||
|
||||
@ -17,39 +59,65 @@ const useFetch = <T>(url: string, params: RequestInit | undefined, initialData:
|
||||
const abortController = new AbortController()
|
||||
abortControllerRef.current = abortController
|
||||
|
||||
fetch(url, { ...params, signal: abortControllerRef.current.signal })
|
||||
const headers = new Headers({
|
||||
...params?.headers
|
||||
})
|
||||
|
||||
if (needAuth) {
|
||||
const token = getToken()
|
||||
|
||||
if (token === null) {
|
||||
return navigate('/login')
|
||||
}
|
||||
|
||||
headers.append('Auth', `Bearer ${token}`)
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method,
|
||||
...params,
|
||||
headers,
|
||||
signal: abortControllerRef.current.signal,
|
||||
})
|
||||
.then(res => {
|
||||
handleHTTPErrors(res)
|
||||
|
||||
return res.json()
|
||||
})
|
||||
.then(data => {
|
||||
if (!dataGuard(data)) {
|
||||
throw new Error('Неверный ответ от сервера')
|
||||
if (!guardResponse(data)) {
|
||||
throw new Error('Malformed server response')
|
||||
}
|
||||
|
||||
setData(data)
|
||||
setData(processData(data))
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(err => {
|
||||
if (err instanceof Error && !isAborted(err)) {
|
||||
setError('Ошибка сети')
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(url, params, err)
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(url, params, err)
|
||||
}
|
||||
})
|
||||
|
||||
return () => abortControllerRef.current?.abort()
|
||||
}, [url, params, dataGuard])
|
||||
}, [url, method, needAuth, params, guardResponse, processData, navigate])
|
||||
|
||||
return {
|
||||
data, loading, error,
|
||||
...(
|
||||
error === null ? ({
|
||||
data: data!, error: null
|
||||
}) : ({ data: undefined, error })
|
||||
),
|
||||
loading,
|
||||
abort: abortControllerRef.current?.abort.bind(abortControllerRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
export default useFetch
|
||||
|
||||
export { gotError }
|
||||
|
@ -1,96 +0,0 @@
|
||||
import useFetch from './useFetch'
|
||||
import { FiltersType, filterNames } from '../../utils/filters'
|
||||
import { isArrayOf, isObject } from '../../utils/types'
|
||||
import { API_URL } from '../../config'
|
||||
import { Category, isCategory } from '../../assets/category'
|
||||
|
||||
const initialAnnouncements = { list_of_announcements: [], Success: true }
|
||||
|
||||
type AnnouncementsListResponse = {
|
||||
list_of_announcements: AnnouncementResponse[],
|
||||
Success: boolean
|
||||
}
|
||||
|
||||
const isAnnouncementsListResponse = (obj: unknown): obj is AnnouncementsListResponse => isObject(obj, {
|
||||
'list_of_announcements': obj => isArrayOf<AnnouncementResponse>(obj, isAnnouncementResponse),
|
||||
'Success': 'boolean'
|
||||
})
|
||||
|
||||
type AnnouncementResponse = {
|
||||
id: number,
|
||||
user_id: number,
|
||||
name: string,
|
||||
category: Category,
|
||||
best_by: number,
|
||||
address: string,
|
||||
longtitude: number,
|
||||
latitude: number,
|
||||
description: string,
|
||||
src: string | null,
|
||||
metro: string,
|
||||
trashId: number | null,
|
||||
booked_by: number
|
||||
}
|
||||
|
||||
const isAnnouncementResponse = (obj: unknown): obj is AnnouncementResponse => isObject(obj, {
|
||||
'id': 'number',
|
||||
'user_id': 'number',
|
||||
'name': 'string',
|
||||
'category': isCategory,
|
||||
'best_by': 'number',
|
||||
'address': 'string',
|
||||
'longtitude': 'number',
|
||||
'latitude': 'number',
|
||||
'description': 'string',
|
||||
'src': 'string?',
|
||||
'metro': 'string',
|
||||
'trashId': 'number?',
|
||||
'booked_by': 'number'
|
||||
})
|
||||
|
||||
type Announcement = {
|
||||
id: number,
|
||||
userId: number,
|
||||
name: string,
|
||||
category: Category,
|
||||
bestBy: number,
|
||||
address: string,
|
||||
lng: number,
|
||||
lat: number,
|
||||
description: string | null,
|
||||
src: string | null,
|
||||
metro: string,
|
||||
trashId: number | null,
|
||||
bookedBy: number
|
||||
}
|
||||
|
||||
const composeFilters = (filters: FiltersType) => Object.fromEntries(
|
||||
filterNames.map(
|
||||
fName => [fName, filters[fName]?.toString()]
|
||||
).filter((p): p is [string, string] => typeof p[1] !== 'undefined')
|
||||
)
|
||||
|
||||
const useHomeAnnouncementList = (filters: FiltersType) => {
|
||||
const { data, loading, error } = useFetch(
|
||||
API_URL + '/announcements?' + new URLSearchParams(composeFilters(filters)).toString(),
|
||||
undefined,
|
||||
initialAnnouncements,
|
||||
isAnnouncementsListResponse
|
||||
)
|
||||
|
||||
const annList = data.list_of_announcements
|
||||
|
||||
const res: Announcement[] = annList.map(ann => ({
|
||||
...ann,
|
||||
lat: ann.latitude,
|
||||
lng: ann.longtitude,
|
||||
bestBy: ann.best_by,
|
||||
bookedBy: ann.booked_by,
|
||||
userId: ann.user_id
|
||||
}))
|
||||
|
||||
return { data: error ? [] : res, loading, error }
|
||||
}
|
||||
|
||||
export type { Announcement, AnnouncementsListResponse }
|
||||
export default useHomeAnnouncementList
|
@ -1,35 +1,18 @@
|
||||
import { LatLng } from 'leaflet'
|
||||
|
||||
import { API_URL } from '../../config'
|
||||
import { isArrayOf, isObject } from '../../utils/types'
|
||||
import useFetch from './useFetch'
|
||||
import { isString } from '../../utils/types'
|
||||
|
||||
type Trashbox = {
|
||||
Lat: number,
|
||||
Lng: number,
|
||||
Address: string,
|
||||
Categories: string[]
|
||||
}
|
||||
|
||||
const isTrashbox = (obj: unknown): obj is Trashbox => isObject(obj, {
|
||||
'Lat': 'number',
|
||||
'Lng': 'number',
|
||||
'Address': 'string',
|
||||
'Categories': obj => isArrayOf<string>(obj, isString)
|
||||
})
|
||||
import { composeTrashboxURL } from '../../api/trashbox'
|
||||
import { isTrashboxResponse } from '../../api/trashbox/types'
|
||||
|
||||
const useTrashboxes = (position: LatLng) => {
|
||||
return useFetch(
|
||||
API_URL + '/trashbox?' + new URLSearchParams({
|
||||
lat: position.lat.toString(),
|
||||
lng: position.lng.toString()
|
||||
}).toString(),
|
||||
undefined,
|
||||
[],
|
||||
(obj): obj is Trashbox[] => isArrayOf(obj, isTrashbox)
|
||||
composeTrashboxURL(position),
|
||||
'GET',
|
||||
true,
|
||||
(data) => data,
|
||||
isTrashboxResponse,
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
export type { Trashbox }
|
||||
export default useTrashboxes
|
||||
|
Reference in New Issue
Block a user