Converted put(api/announcement) to use useSend
Added useSendButtonCaption hook Related to #19
This commit is contained in:
parent
b360b06d34
commit
b7bbd937b4
12
front/src/api/putAnnouncement/index.ts
Normal file
12
front/src/api/putAnnouncement/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { API_URL } from '../../config'
|
||||
import { PutAnnouncement, PutAnnouncementResponse } from './types'
|
||||
|
||||
const composePutAnnouncementURL = () => (
|
||||
API_URL + '/announcement?'
|
||||
)
|
||||
|
||||
const processPutAnnouncement = (data: PutAnnouncementResponse): PutAnnouncement => {
|
||||
return data.Answer
|
||||
}
|
||||
|
||||
export { composePutAnnouncementURL, processPutAnnouncement }
|
17
front/src/api/putAnnouncement/types.ts
Normal file
17
front/src/api/putAnnouncement/types.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { isObject } from '../../utils/types'
|
||||
|
||||
type PutAnnouncementResponse = {
|
||||
Answer: boolean
|
||||
}
|
||||
|
||||
const isPutAnnouncementResponse = (obj: unknown): obj is PutAnnouncementResponse => (
|
||||
isObject(obj, {
|
||||
'Answer': 'boolean'
|
||||
})
|
||||
)
|
||||
|
||||
type PutAnnouncement = boolean
|
||||
|
||||
export type { PutAnnouncementResponse, PutAnnouncement }
|
||||
|
||||
export { isPutAnnouncementResponse }
|
@ -1,79 +1,31 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { API_URL } from '../../config'
|
||||
import { isLiteralUnion } from '../../utils/types'
|
||||
import { handleHTTPErrors } from '../../utils'
|
||||
|
||||
const addErrors = ['Не удалось опубликовать объявление', 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
|
||||
type AddError = typeof addErrors[number]
|
||||
|
||||
const isAddError = (obj: unknown): obj is AddError => (
|
||||
isLiteralUnion(obj, addErrors)
|
||||
)
|
||||
|
||||
const buttonStates = ['Опубликовать', 'Загрузка...', 'Опубликовано', 'Отменено'] as const
|
||||
type ButtonState = typeof buttonStates[number] | AddError
|
||||
|
||||
type AddResponse = {
|
||||
Answer: boolean
|
||||
}
|
||||
|
||||
const isAddResponse = (obj: unknown): obj is AddResponse => (
|
||||
typeof obj === 'object' && obj !== null && typeof Reflect.get(obj, 'Answer') === 'boolean'
|
||||
)
|
||||
import { useSend } from '..'
|
||||
import { composePutAnnouncementURL, processPutAnnouncement } from '../../api/putAnnouncement'
|
||||
import { isPutAnnouncementResponse } from '../../api/putAnnouncement/types'
|
||||
import useSendButtonCaption from '../useSendButtonCaption'
|
||||
|
||||
const useAddAnnouncement = () => {
|
||||
const [status, setStatus] = useState<ButtonState>('Опубликовать')
|
||||
const { doSend, loading, error } = useSend(
|
||||
composePutAnnouncementURL(),
|
||||
'PUT',
|
||||
true,
|
||||
isPutAnnouncementResponse,
|
||||
processPutAnnouncement,
|
||||
)
|
||||
|
||||
const timerIdRef = useRef<number>()
|
||||
const abortControllerRef = useRef<AbortController>()
|
||||
const { update, ...button } = useSendButtonCaption('Опубликовать', loading, error, 'Опубликовано')
|
||||
|
||||
const doAdd = async (formData: FormData) => {
|
||||
if (status === 'Загрузка...') {
|
||||
abortControllerRef.current?.abort()
|
||||
setStatus('Отменено')
|
||||
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 3000)
|
||||
return
|
||||
}
|
||||
const doSendWithButton = useCallback(async (formData: FormData) => {
|
||||
const data = await doSend({}, {
|
||||
body: formData
|
||||
})
|
||||
update(data)
|
||||
|
||||
setStatus('Загрузка...')
|
||||
return data
|
||||
}, [doSend, update])
|
||||
|
||||
const abortController = new AbortController()
|
||||
abortControllerRef.current = abortController
|
||||
|
||||
try {
|
||||
const res = await fetch(API_URL + '/announcement', {
|
||||
method: 'PUT',
|
||||
body: formData,
|
||||
signal: abortController.signal
|
||||
})
|
||||
|
||||
handleHTTPErrors(res)
|
||||
|
||||
const data: unknown = await res.json()
|
||||
|
||||
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
|
||||
|
||||
if (!data.Answer) {
|
||||
throw new Error('Не удалось опубликовать объявление')
|
||||
}
|
||||
setStatus('Опубликовано')
|
||||
|
||||
} catch (err) {
|
||||
setStatus(isAddError(err) ? err : 'Неизвестная ошибка')
|
||||
timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 10000)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const abortController = abortControllerRef.current
|
||||
return () => {
|
||||
clearTimeout(timerIdRef.current)
|
||||
abortController?.abort()
|
||||
}
|
||||
})
|
||||
|
||||
return { doAdd, status }
|
||||
return { doSend: doSendWithButton, button }
|
||||
}
|
||||
|
||||
export default useAddAnnouncement
|
||||
|
@ -24,6 +24,7 @@ function useSend<R, T>(
|
||||
/** Don't use in useEffect. If you need request result, go with useFetch instead */
|
||||
const doSend = useCallback(async (urlProps?: Record<string, string>, params?: Omit<RequestInit, 'method'>) => {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
if (abortControllerRef.current) {
|
||||
abortControllerRef.current.abort()
|
||||
|
44
front/src/hooks/useSendButtonCaption.ts
Normal file
44
front/src/hooks/useSendButtonCaption.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
function useSendButtonCaption(
|
||||
initial: string,
|
||||
loading: boolean,
|
||||
error: string | null,
|
||||
result = initial,
|
||||
singular = true
|
||||
) {
|
||||
const [caption, setCaption] = useState(initial)
|
||||
const [disabled, setDisabled] = useState(false)
|
||||
const [title, setTitle] = useState(initial)
|
||||
|
||||
const update = useCallback(<T extends NonNullable<unknown>>(data: T | undefined) => {
|
||||
if (data !== undefined) {
|
||||
setCaption(result)
|
||||
setTitle('Отправить ещё раз')
|
||||
|
||||
if (singular) {
|
||||
setDisabled(true)
|
||||
setTitle('')
|
||||
}
|
||||
}
|
||||
|
||||
}, [result, singular])
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setCaption('Загрузка...')
|
||||
setTitle('Отменить и отправить ещё раз')
|
||||
}
|
||||
}, [loading])
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && error !== null) {
|
||||
setCaption(error + ', нажмите, чтобы попробовать ещё раз')
|
||||
setTitle('')
|
||||
}
|
||||
}, [error, loading])
|
||||
|
||||
return { update, children: caption, disabled, title }
|
||||
}
|
||||
|
||||
export default useSendButtonCaption
|
@ -1,11 +1,10 @@
|
||||
import { CSSProperties, FormEventHandler, useEffect, useState } from 'react'
|
||||
import { CSSProperties, FormEventHandler, useState } from 'react'
|
||||
import { Form, Button, Card } from 'react-bootstrap'
|
||||
import { MapContainer, TileLayer } from 'react-leaflet'
|
||||
import { latLng } from 'leaflet'
|
||||
|
||||
import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
|
||||
import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
|
||||
import { handleHTTPErrors } from '../utils'
|
||||
import { categories, categoryNames } from '../assets/category'
|
||||
import { stations, lines, lineNames } from '../assets/metro'
|
||||
import { fallbackError, gotError } from '../hooks/useFetch'
|
||||
@ -32,25 +31,7 @@ function AddPage() {
|
||||
|
||||
const address = useOsmAddresses(addressPosition)
|
||||
|
||||
useEffect(() => {
|
||||
if (!gotError(address))
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(address.data))
|
||||
|
||||
handleHTTPErrors(res)
|
||||
|
||||
const fetchData: unknown = await res.json()
|
||||
|
||||
console.log('f', fetchData)
|
||||
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})()
|
||||
}, [address])
|
||||
|
||||
const { doAdd, status } = useAddAnnouncement()
|
||||
const { doSend, button } = useAddAnnouncement()
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault()
|
||||
@ -63,7 +44,7 @@ function AddPage() {
|
||||
formData.append('address', address.data || '') // if address.error
|
||||
formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
|
||||
|
||||
void doAdd(formData)
|
||||
void doSend(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -151,7 +132,7 @@ function AddPage() {
|
||||
</Form.Select>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group className='mb-3' controlId='password'>
|
||||
<Form.Group className='mb-3' controlId='trashbox'>
|
||||
<Form.Label>Пункт сбора мусора</Form.Label>
|
||||
<div className='mb-3'>
|
||||
{trashboxes.loading
|
||||
@ -195,9 +176,7 @@ function AddPage() {
|
||||
)}
|
||||
</Form.Group>
|
||||
|
||||
<Button variant='success' type='submit'>
|
||||
{status}
|
||||
</Button>
|
||||
<Button variant='success' type='submit' {...button} />
|
||||
</Form>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
Loading…
x
Reference in New Issue
Block a user