forked from polka_billy/porridger
Added TypeScript for frontend
Added type definitions for components, functions, data Added guards for network responses fixes #8
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { FormEventHandler, useEffect, useState } from "react"
|
||||
import { Form, Button, Card } from "react-bootstrap"
|
||||
import { MapContainer, TileLayer } from 'react-leaflet'
|
||||
import { latLng } from "leaflet"
|
||||
@ -7,21 +7,22 @@ import { ClickHandler, LocationMarker, TrashboxMarkers } from "../components"
|
||||
import { useAddAnnouncement, useTrashboxes } from "../hooks/api"
|
||||
|
||||
import { categoryNames } from "../assets/category"
|
||||
import { stations, lines } from "../assets/metro"
|
||||
import { stations, lines, lineNames } from "../assets/metro"
|
||||
import { isObject } from "../utils/types"
|
||||
|
||||
function AddPage() {
|
||||
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
|
||||
const [address, setAddress] = useState('')
|
||||
|
||||
const { data: trashboxes, trashboxes_loading, trashboxes_error } = useTrashboxes(addressPosition)
|
||||
const trashboxes = useTrashboxes(addressPosition)
|
||||
const [selectedTrashbox, setSelectedTrashbox] = useState({ index: -1, category: '' })
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await fetch(location.protocol + "//nominatim.openstreetmap.org/search?format=json&q=" + address)
|
||||
|
||||
const fetchData = await res.json()
|
||||
const fetchData: unknown = await res.json()
|
||||
|
||||
console.log("f", fetchData)
|
||||
|
||||
@ -32,11 +33,15 @@ function AddPage() {
|
||||
}, [address])
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
void (async () => {
|
||||
try {
|
||||
const res = await fetch(location.protocol + "//nominatim.openstreetmap.org/reverse?format=json&accept-language=ru&lat=" + addressPosition.lat + "&lon=" + addressPosition.lng)
|
||||
const res = await fetch(`${location.protocol}//nominatim.openstreetmap.org/reverse?format=json&accept-language=ru&lat=${addressPosition.lat}&lon=${addressPosition.lng}`)
|
||||
|
||||
const fetchData = await res.json()
|
||||
const fetchData: unknown = await res.json()
|
||||
|
||||
if (!isObject<{ display_name: string }>(fetchData, { "display_name": "string" })) {
|
||||
throw new Error("Malformed server response")
|
||||
}
|
||||
|
||||
setAddress(fetchData.display_name)
|
||||
|
||||
@ -48,18 +53,18 @@ function AddPage() {
|
||||
|
||||
const { doAdd, status } = useAddAnnouncement()
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const formData = new FormData(event.target)
|
||||
const formData = new FormData(event.currentTarget)
|
||||
|
||||
formData.append("latitude", addressPosition.lat)
|
||||
formData.append("longtitude", addressPosition.lng)
|
||||
formData.append("latitude", addressPosition.lat.toString())
|
||||
formData.append("longtitude", addressPosition.lng.toString())
|
||||
formData.append("address", address)
|
||||
formData.set("bestBy", new Date(formData.get("bestBy")).getTime())
|
||||
formData.set("bestBy", new Date((formData.get("bestBy") as number | null) || 0).getTime().toString())
|
||||
|
||||
doAdd(formData)
|
||||
void doAdd(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -137,10 +142,10 @@ function AddPage() {
|
||||
<option value="">
|
||||
Укажите ближайщую станцию метро
|
||||
</option>
|
||||
{Object.entries(stations).map(
|
||||
([line, stations]) =>
|
||||
<optgroup key={line} label={lines[line]}>
|
||||
{Array.from(stations).map(metro =>
|
||||
{lines.map(
|
||||
line =>
|
||||
<optgroup key={line} label={lineNames[line]}>
|
||||
{Array.from(stations[line]).map(metro =>
|
||||
<option key={metro} value={metro}>{metro}</option>
|
||||
)}
|
||||
</optgroup>
|
||||
@ -151,17 +156,17 @@ function AddPage() {
|
||||
<Form.Group className="mb-3" controlId="password">
|
||||
<Form.Label>Пункт сбора мусора</Form.Label>
|
||||
<div className="mb-3">
|
||||
{trashboxes_loading
|
||||
{trashboxes.loading
|
||||
? (
|
||||
<div style={{ height: 400 }}>
|
||||
<p>Загрузка</p>
|
||||
</div>
|
||||
) : (
|
||||
trashboxes_error ? (
|
||||
trashboxes.error ? (
|
||||
<p
|
||||
style={{ height: 400 }}
|
||||
className="text-danger"
|
||||
>{trashboxes_error}</p>
|
||||
>{trashboxes.error}</p>
|
||||
) : (
|
||||
<MapContainer
|
||||
scrollWheelZoom={false}
|
||||
@ -175,7 +180,7 @@ function AddPage() {
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<TrashboxMarkers
|
||||
trashboxes={trashboxes}
|
||||
trashboxes={trashboxes.data}
|
||||
selectTrashbox={setSelectedTrashbox}
|
||||
/>
|
||||
</MapContainer>
|
||||
@ -185,7 +190,7 @@ function AddPage() {
|
||||
</div>
|
||||
{selectedTrashbox.index > -1 ? (
|
||||
<p>Выбран пункт сбора мусора на {
|
||||
trashboxes[selectedTrashbox.index].Address
|
||||
trashboxes.data[selectedTrashbox.index].Address
|
||||
} с категорией {selectedTrashbox.category}</p>
|
||||
) : (
|
||||
<p>Выберите пунк сбора мусора и категорию</p>
|
@ -4,22 +4,25 @@ import Stories from 'react-insta-stories'
|
||||
import { BottomNavBar, AnnouncementDetails, Filters } from '../components'
|
||||
import { useStoryDimensions } from '../hooks'
|
||||
import { useHomeAnnouncementList } from '../hooks/api'
|
||||
import { defaultFilters } from '../utils/filters'
|
||||
|
||||
import puffSpinner from '../assets/puff.svg'
|
||||
import { categoryGraphics } from '../assets/category'
|
||||
import { Announcement } from '../hooks/api/useHomeAnnouncementList'
|
||||
import { Story } from 'react-insta-stories/dist/interfaces'
|
||||
|
||||
function generateStories(announcements) {
|
||||
function generateStories(announcements: Announcement[]): Story[] {
|
||||
return announcements.map(announcement => {
|
||||
return ({
|
||||
id: announcement.id,
|
||||
url: announcement.src || categoryGraphics.get(announcement.category),
|
||||
type: announcement.src?.endsWith("mp4") ? "video" : undefined,
|
||||
seeMore: ({ close }) => <AnnouncementDetails close={close} announcement={announcement} />
|
||||
seeMore: ({ close }: { close: () => void }) => <AnnouncementDetails close={close} announcement={announcement} />
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function fallbackGenerateStories(announcementsFetch) {
|
||||
function fallbackGenerateStories(announcementsFetch: ReturnType<typeof useHomeAnnouncementList>) {
|
||||
const stories = generateStories(announcementsFetch.data)
|
||||
|
||||
if (announcementsFetch.loading)
|
||||
@ -34,7 +37,7 @@ function fallbackGenerateStories(announcementsFetch) {
|
||||
return stories
|
||||
}
|
||||
|
||||
const fallbackStory = (text, isError = false) => [{
|
||||
const fallbackStory = (text = '', isError = false): Story[] => [{
|
||||
content: ({ action }) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => { action('pause') }, [action])
|
||||
@ -45,11 +48,8 @@ const fallbackStory = (text, isError = false) => [{
|
||||
</div>
|
||||
)
|
||||
},
|
||||
header: { heading: text }
|
||||
}]
|
||||
|
||||
const defaultFilters = { userId: null, category: null, metro: null, bookedBy: null }
|
||||
|
||||
function HomePage() {
|
||||
const { height, width } = useStoryDimensions(16 / 10)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { FormEventHandler } from 'react'
|
||||
import { Form, Button, Card, Tabs, Tab } from "react-bootstrap"
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@ -7,25 +8,27 @@ import { setToken } from "../utils/auth";
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const doAuth = useAuth()
|
||||
const { doAuth } = useAuth() // TODO: Add loading and error handling
|
||||
|
||||
const handleAuth = (newAccount) => (event) => {
|
||||
const handleAuth = (newAccount: boolean): FormEventHandler<HTMLFormElement> => async (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const formData = new FormData(event.currentTarget)
|
||||
|
||||
const data = {
|
||||
email: formData.get('email'),
|
||||
name: newAccount ? formData.get('name') : undefined,
|
||||
password: formData.get('password')
|
||||
email: formData.get('email') as string,
|
||||
name: newAccount ? formData.get('name') as string : undefined,
|
||||
surname: newAccount ? formData.get('surname') as string : undefined,
|
||||
password: formData.get('password') as string
|
||||
}
|
||||
|
||||
const token = "a" // doAuth(data, newAccount)
|
||||
const token = import.meta.env.PROD ? await doAuth(data, newAccount) : "a"
|
||||
|
||||
setToken(token)
|
||||
|
||||
navigate("/")
|
||||
if (token) {
|
||||
setToken(token)
|
||||
navigate("/")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
@ -1,7 +0,0 @@
|
||||
function UserPage() {
|
||||
/* TODO */
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default UserPage
|
9
front/src/pages/UserPage.tsx
Normal file
9
front/src/pages/UserPage.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
function UserPage() {
|
||||
/* TODO */
|
||||
|
||||
return <h1>For Yet Go <Link to="/">Home</Link></h1>
|
||||
}
|
||||
|
||||
export default UserPage
|
Reference in New Issue
Block a user