Code styling changes

This commit is contained in:
Dmitriy Shishkov 2023-07-13 18:38:31 +03:00
parent 9437c44054
commit 7b0ccc525c
Signed by: dm1sh
GPG Key ID: 027994B0AA357688
33 changed files with 358 additions and 387 deletions

View File

@ -23,5 +23,14 @@ module.exports = {
{ allowConstantExport: true }, { allowConstantExport: true },
], ],
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/quotes': [
'error',
'single',
{
'avoidEscape': true,
'allowTemplateLiterals': true
}
],
'jsx-quotes': [2, 'prefer-single'],
}, },
} }

24
front/.gitignore vendored
View File

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,11 +1,9 @@
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import { HomePage, AddPage, LoginPage, UserPage } from './pages' import { HomePage, AddPage, LoginPage, UserPage } from './pages'
import { WithToken } from './components' import { WithToken } from './components'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import './App.css' import './App.css'
function App() { function App() {
@ -13,17 +11,17 @@ function App() {
<Router> <Router>
<Routes> <Routes>
<Route index element={<HomePage />} /> <Route index element={<HomePage />} />
<Route path="/add" element={ <Route path='/add' element={
<WithToken> <WithToken>
<AddPage /> <AddPage />
</WithToken> </WithToken>
} /> } />
<Route path="/user" element={ <Route path='/user' element={
<WithToken> <WithToken>
<UserPage /> <UserPage />
</WithToken> </WithToken>
} /> } />
<Route path="/login" element={<LoginPage />} /> <Route path='/login' element={<LoginPage />} />
</Routes> </Routes>
</Router> </Router>
) )

View File

@ -1,39 +1,39 @@
import { isLiteralUnion } from "../utils/types" import { isLiteralUnion } from '../utils/types'
const categories = ["PORRIDGE", "conspects", "milk", "bred", "wathing", "cloth", const categories = ['PORRIDGE', 'conspects', 'milk', 'bred', 'wathing', 'cloth',
"fruits_vegatables", "soup", "dinner", "conserves", "pens", "other_things"] as const 'fruits_vegatables', 'soup', 'dinner', 'conserves', 'pens', 'other_things'] as const
type Category = typeof categories[number] type Category = typeof categories[number]
const isCategory = (obj: unknown): obj is Category => isLiteralUnion(obj, categories) const isCategory = (obj: unknown): obj is Category => isLiteralUnion(obj, categories)
const categoryGraphics: Record<Category, string> = { const categoryGraphics: Record<Category, string> = {
"PORRIDGE": "static/PORRIDGE.jpg", 'PORRIDGE': 'static/PORRIDGE.jpg',
"conspects": "static/conspects.jpg", 'conspects': 'static/conspects.jpg',
"milk": "static/milk.jpg", 'milk': 'static/milk.jpg',
"bred": "static/bred.jpg", 'bred': 'static/bred.jpg',
"wathing": "static/wathing.jpg", 'wathing': 'static/wathing.jpg',
"cloth": "static/cloth.jpg", 'cloth': 'static/cloth.jpg',
"fruits_vegatables": "static/fruits_vegatables.jpg", 'fruits_vegatables': 'static/fruits_vegatables.jpg',
"soup": "static/soup.jpg", 'soup': 'static/soup.jpg',
"dinner": "static/dinner.jpg", 'dinner': 'static/dinner.jpg',
"conserves": "static/conserves.jpg", 'conserves': 'static/conserves.jpg',
"pens": "static/pens.jpg", 'pens': 'static/pens.jpg',
"other_things": "static/other_things.jpg", 'other_things': 'static/other_things.jpg',
} }
const categoryNames: Record<Category, string> = { const categoryNames: Record<Category, string> = {
"PORRIDGE": "PORRIDGE", 'PORRIDGE': 'PORRIDGE',
"conspects": "Конспекты", 'conspects': 'Конспекты',
"milk": "Молочные продукты", 'milk': 'Молочные продукты',
"bred": "Хлебобулочные изделия", 'bred': 'Хлебобулочные изделия',
"wathing": "Моющие средства", 'wathing': 'Моющие средства',
"cloth": "Одежда", 'cloth': 'Одежда',
"fruits_vegatables": "Фрукты и овощи", 'fruits_vegatables': 'Фрукты и овощи',
"soup": "Супы", 'soup': 'Супы',
"dinner": "Ужин", 'dinner': 'Ужин',
"conserves": "Консервы", 'conserves': 'Консервы',
"pens": "Канцелярия", 'pens': 'Канцелярия',
"other_things": "Всякая всячина", 'other_things': 'Всякая всячина',
} }
export type { Category } export type { Category }

View File

@ -3,102 +3,102 @@ type Lines = typeof lines[number]
const stations: Record<Lines, Set<string>> = { const stations: Record<Lines, Set<string>> = {
red: new Set([ red: new Set([
"Девяткино", 'Девяткино',
"Гражданский проспект", 'Гражданский проспект',
"Академическая", 'Академическая',
"Политехническая", 'Политехническая',
"Площадь Мужества", 'Площадь Мужества',
"Лесная", 'Лесная',
"Выборгская", 'Выборгская',
"Площадь Ленина", 'Площадь Ленина',
"Чернышевская", 'Чернышевская',
"Площадь Восстания", 'Площадь Восстания',
"Владимирская", 'Владимирская',
"Пушкинская", 'Пушкинская',
"Технологический институт", 'Технологический институт',
"Балтийская", 'Балтийская',
"Нарвская", 'Нарвская',
"Кировский завод", 'Кировский завод',
"Автово", 'Автово',
"Ленинский проспект", 'Ленинский проспект',
"Проспект Ветеранов" 'Проспект Ветеранов'
]), ]),
blue: new Set([ blue: new Set([
"Парнас", 'Парнас',
"Проспект Просвещения", 'Проспект Просвещения',
"Озерки", 'Озерки',
"Удельная", 'Удельная',
"Пионерская", 'Пионерская',
"Чёрная речка", 'Чёрная речка',
"Петроградская", 'Петроградская',
"Горьковская", 'Горьковская',
"Невский проспект", 'Невский проспект',
"Сенная площадь", 'Сенная площадь',
"Технологический институт", 'Технологический институт',
"Фрунзенская", 'Фрунзенская',
"Московские ворота", 'Московские ворота',
"Электросила", 'Электросила',
"Парк Победы", 'Парк Победы',
"Московская", 'Московская',
"Звёздная", 'Звёздная',
"Купчино" 'Купчино'
]), ]),
green: new Set([ green: new Set([
"Приморская", 'Приморская',
"Беговая", 'Беговая',
"Василеостровская", 'Василеостровская',
"Гостиный двор", 'Гостиный двор',
"Маяковская", 'Маяковская',
"Площадь Александра Невского", 'Площадь Александра Невского',
"Елизаровская", 'Елизаровская',
"Ломоносовская", 'Ломоносовская',
"Пролетарская", 'Пролетарская',
"Обухово", 'Обухово',
"Рыбацкое" 'Рыбацкое'
]), ]),
orange: new Set([ orange: new Set([
"Спасская", 'Спасская',
"Достоевская", 'Достоевская',
"Лиговский проспект", 'Лиговский проспект',
"Площадь Александра Невского", 'Площадь Александра Невского',
"Новочеркасская", 'Новочеркасская',
"Ладожская", 'Ладожская',
"Проспект Большевиков", 'Проспект Большевиков',
"Улица Дыбенко" 'Улица Дыбенко'
]), ]),
violet: new Set([ violet: new Set([
"Комендантский проспект", 'Комендантский проспект',
"Старая Деревня", 'Старая Деревня',
"Крестовский остров", 'Крестовский остров',
"Чкаловская", 'Чкаловская',
"Спортивная", 'Спортивная',
"Адмиралтейская", 'Адмиралтейская',
"Садовая", 'Садовая',
"Звенигородская", 'Звенигородская',
"Обводный канал", 'Обводный канал',
"Волковская", 'Волковская',
"Бухарестская", 'Бухарестская',
"Международная", 'Международная',
"Проспект славы", 'Проспект славы',
"Дунайскай", 'Дунайскай',
"Шушары" 'Шушары'
]), ]),
} }
const colors: Record<Lines, string> = { const colors: Record<Lines, string> = {
red: "#D6083B", red: '#D6083B',
blue: "#0078C9", blue: '#0078C9',
green: "#009A49", green: '#009A49',
orange: "#EA7125", orange: '#EA7125',
violet: "#702785", violet: '#702785',
} }
const lineNames: Record<Lines, string> = { const lineNames: Record<Lines, string> = {
red: "Красная", red: 'Красная',
blue: "Синяя", blue: 'Синяя',
green: "Зелёная", green: 'Зелёная',
orange: "Оранжевая", orange: 'Оранжевая',
violet: "Фиолетовая", violet: 'Фиолетовая',
} }
const lineByName = (name: string) => const lineByName = (name: string) =>

View File

@ -16,10 +16,10 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
return ( return (
<div <div
className="modal" className='modal'
style={{ display: 'flex', alignItems: "center", justifyContent: "center" }} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}
> >
<Modal.Dialog style={{ minWidth: "50vw" }}> <Modal.Dialog style={{ minWidth: '50vw' }}>
<Modal.Header closeButton onHide={close}> <Modal.Header closeButton onHide={close}>
<Modal.Title> <Modal.Title>
Подробнее Подробнее
@ -35,10 +35,10 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
<p className='mb-3'>{description}</p> <p className='mb-3'>{description}</p>
<MapContainer style={{ width: "100%", minHeight: 300 }} center={[lat, lng]} zoom={16} > <MapContainer style={{ width: '100%', minHeight: 300 }} center={[lat, lng]} zoom={16} >
<TileLayer <TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
<Marker position={[lat, lng]}> <Marker position={[lat, lng]}>
@ -53,7 +53,7 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
<Modal.Footer> <Modal.Footer>
<Button variant='success' onClick={() => void handleBook()}> <Button variant='success' onClick={() => void handleBook()}>
{bookStatus || "Забронировать"} {bookStatus || 'Забронировать'}
</Button> </Button>
</Modal.Footer> </Modal.Footer>
</Modal.Dialog> </Modal.Dialog>
@ -61,4 +61,4 @@ function AnnouncementDetails({ close, announcement: { id, name, category, bestBy
) )
} }
export default AnnouncementDetails export default AnnouncementDetails

View File

@ -1,5 +1,5 @@
import { FormEventHandler } from "react" import { FormEventHandler } from 'react'
import { Button, Form } from "react-bootstrap" import { Button, Form } from 'react-bootstrap'
type AuthFormProps = { type AuthFormProps = {
register: boolean register: boolean
@ -9,43 +9,43 @@ type AuthFormProps = {
} }
const AuthForm = ({ handleAuth, register, loading, error }: AuthFormProps) => { const AuthForm = ({ handleAuth, register, loading, error }: AuthFormProps) => {
const buttonText = loading ? "Загрузка..." : (error || (register ? "Зарегистрироваться" : "Войти")) const buttonText = loading ? 'Загрузка...' : (error || (register ? 'Зарегистрироваться' : 'Войти'))
return ( return (
<Form onSubmit={handleAuth}> <Form onSubmit={handleAuth}>
<Form.Group className="mb-3" controlId="email"> <Form.Group className='mb-3' controlId='email'>
<Form.Label>Почта</Form.Label> <Form.Label>Почта</Form.Label>
<Form.Control type="email" required /> <Form.Control type='email' required />
</Form.Group> </Form.Group>
{register && <> {register && <>
<Form.Group className="mb-3" controlId="name"> <Form.Group className='mb-3' controlId='name'>
<Form.Label>Имя</Form.Label> <Form.Label>Имя</Form.Label>
<Form.Control type="text" required /> <Form.Control type='text' required />
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="surname"> <Form.Group className='mb-3' controlId='surname'>
<Form.Label>Фамилия</Form.Label> <Form.Label>Фамилия</Form.Label>
<Form.Control type="text" required /> <Form.Control type='text' required />
</Form.Group> </Form.Group>
</>} </>}
<Form.Group className="mb-3" controlId="password"> <Form.Group className='mb-3' controlId='password'>
<Form.Label>Пароль</Form.Label> <Form.Label>Пароль</Form.Label>
<Form.Control type="password" required /> <Form.Control type='password' required />
</Form.Group> </Form.Group>
{register && {register &&
<Form.Group className="mb-3" controlId="privacyPolicyConsent"> <Form.Group className='mb-3' controlId='privacyPolicyConsent'>
<Form.Check label="<a>условиями обработки персональных данных</a>"> <Form.Check>
<Form.Check.Input type="checkbox" required /> <Form.Check.Input type='checkbox' required />
<Form.Check.Label> <Form.Check.Label>
Я согласен с <a href={`${document.location.origin}/privacy_policy.pdf`} target="_blank" rel="noopener noreferrer">условиями обработки персональных данных</a> Я согласен с <a href={`${document.location.origin}/privacy_policy.pdf`} target='_blank' rel='noopener noreferrer'>условиями обработки персональных данных</a>
</Form.Check.Label> </Form.Check.Label>
</Form.Check> </Form.Check>
</Form.Group> </Form.Group>
} }
<Button variant="success" type="submit"> <Button variant='success' type='submit'>
{buttonText} {buttonText}
</Button> </Button>
</Form> </Form>

View File

@ -7,22 +7,22 @@ import userIcon from '../assets/userIcon.svg'
const navBarStyles: React.CSSProperties = { const navBarStyles: React.CSSProperties = {
backgroundColor: 'var(--bs-success)', backgroundColor: 'var(--bs-success)',
height: 56, height: 56,
width: "100%", width: '100%',
} }
const navBarGroupStyles: React.CSSProperties = { const navBarGroupStyles: React.CSSProperties = {
display: "flex", display: 'flex',
flexDirection: "row", flexDirection: 'row',
height: "100%", height: '100%',
margin: "auto" margin: 'auto'
} }
const navBarElementStyles: React.CSSProperties = { const navBarElementStyles: React.CSSProperties = {
width: "100%", width: '100%',
height: "100%", height: '100%',
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center" justifyContent: 'center'
} }
type BottomNavBarProps = { type BottomNavBarProps = {
@ -36,15 +36,15 @@ function BottomNavBar({ width, toggleFilters }: BottomNavBarProps) {
<div style={{ ...navBarGroupStyles, width: width }}> <div style={{ ...navBarGroupStyles, width: width }}>
<a style={navBarElementStyles} onClick={() => toggleFilters(true)}> <a style={navBarElementStyles} onClick={() => toggleFilters(true)}>
<img src={filterIcon} alt="Фильтровать объявления" title='Фильтровать объявления' /> <img src={filterIcon} alt='Фильтровать объявления' title='Фильтровать объявления' />
</a> </a>
<Link style={navBarElementStyles} to="/add" > <Link style={navBarElementStyles} to='/add' >
<img src={addIcon} alt="Опубликовать объявление" title='Опубликовать объявление' /> <img src={addIcon} alt='Опубликовать объявление' title='Опубликовать объявление' />
</Link> </Link>
<Link style={navBarElementStyles} to={"/user"} > <Link style={navBarElementStyles} to={'/user'} >
<img src={userIcon} alt="Личный кабинет" title='Личный кабинет' /> <img src={userIcon} alt='Личный кабинет' title='Личный кабинет' />
</Link> </Link>
</div> </div>

View File

@ -1,6 +1,7 @@
import { useMapEvent } from "react-leaflet" import { useMapEvent } from 'react-leaflet'
import { SetState } from "../utils/types" import { LatLng } from 'leaflet'
import { LatLng } from "leaflet"
import { SetState } from '../utils/types'
function ClickHandler({ setPosition }: { setPosition: SetState<LatLng> }) { function ClickHandler({ setPosition }: { setPosition: SetState<LatLng> }) {
const map = useMapEvent('click', (e) => { const map = useMapEvent('click', (e) => {

View File

@ -1,10 +1,10 @@
import { Button, Form, Modal } from "react-bootstrap" import { Button, Form, Modal } from 'react-bootstrap'
import { FormEventHandler } from 'react'
import { categories, categoryNames } from "../assets/category" import { categories, categoryNames } from '../assets/category'
import { stations, lines, lineNames } from '../assets/metro' import { stations, lines, lineNames } from '../assets/metro'
import { FiltersType } from "../utils/filters" import { FiltersType } from '../utils/filters'
import { SetState } from "../utils/types" import { SetState } from '../utils/types'
import { FormEventHandler } from "react"
type FiltersProps = { type FiltersProps = {
filter: FiltersType, filter: FiltersType,
@ -23,8 +23,8 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }: FiltersProp
setFilter(prev => ({ setFilter(prev => ({
...prev, ...prev,
category: (formData.get("category") as (FiltersType['category'] | null)) || undefined, category: (formData.get('category') as (FiltersType['category'] | null)) || undefined,
metro: (formData.get("metro") as (FiltersType['metro'] | null)) || undefined metro: (formData.get('metro') as (FiltersType['metro'] | null)) || undefined
})) }))
setFilterShown(false) setFilterShown(false)
@ -40,13 +40,13 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }: FiltersProp
<Modal.Body> <Modal.Body>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="categoryFilter"> <Form.Group className='mb-3' controlId='categoryFilter'>
<Form.Label> <Form.Label>
Категория Категория
</Form.Label> </Form.Label>
<Form.Select name="category" defaultValue={filter.category || undefined}> <Form.Select name='category' defaultValue={filter.category || undefined}>
<option value=""> <option value=''>
Выберите категорию Выберите категорию
</option> </option>
{categories.map( {categories.map(
@ -56,13 +56,13 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }: FiltersProp
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="metroFilter"> <Form.Group className='mb-3' controlId='metroFilter'>
<Form.Label> <Form.Label>
Станция метро Станция метро
</Form.Label> </Form.Label>
<Form.Select name="metro" defaultValue={filter.metro || undefined}> <Form.Select name='metro' defaultValue={filter.metro || undefined}>
<option value=""> <option value=''>
Выберите станцию метро Выберите станцию метро
</option> </option>
{lines.map( {lines.map(
@ -76,7 +76,7 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }: FiltersProp
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<Button variant="success" type="submit"> <Button variant='success' type='submit'>
Отправить Отправить
</Button> </Button>
</Form> </Form>
@ -85,4 +85,4 @@ function Filters({ filter, setFilter, filterShown, setFilterShown }: FiltersProp
) )
} }
export default Filters export default Filters

View File

@ -1,6 +1,7 @@
import { Marker, Popup, useMapEvents } from "react-leaflet" import { Marker, Popup, useMapEvents } from 'react-leaflet'
import { LatLng } from 'leaflet' import { LatLng } from 'leaflet'
import { SetState } from "../utils/types"
import { SetState } from '../utils/types'
type LocationMarkerProps = { type LocationMarkerProps = {
address: string, address: string,
@ -32,4 +33,4 @@ const LocationMarker = ({ address, position, setPosition }: LocationMarkerProps)
) )
} }
export default LocationMarker export default LocationMarker

View File

@ -1,5 +1,6 @@
import { Marker, Popup } from "react-leaflet" import { Marker, Popup } from 'react-leaflet'
import { Trashbox } from "../hooks/api/useTrashboxes"
import { Trashbox } from '../hooks/api/useTrashboxes'
type TrashboxMarkersProps = { type TrashboxMarkersProps = {
trashboxes: Trashbox[], trashboxes: Trashbox[],
@ -15,7 +16,7 @@ const TrashboxMarkers = ({ trashboxes, selectTrashbox }: TrashboxMarkersProps) =
<p>Тип мусора: <> <p>Тип мусора: <>
{trashbox.Categories.map((category, j) => {trashbox.Categories.map((category, j) =>
<span key={trashbox.Address + category}> <span key={trashbox.Address + category}>
<a href="#" onClick={() => selectTrashbox({ index, category })}> <a href='#' onClick={() => selectTrashbox({ index, category })}>
{category} {category}
</a> </a>
{(j < trashbox.Categories.length - 1) ? ', ' : ''} {(j < trashbox.Categories.length - 1) ? ', ' : ''}

View File

@ -1,13 +1,14 @@
import { PropsWithChildren, useEffect } from "react" import { PropsWithChildren, useEffect } from 'react'
import { getToken } from "../utils/auth" import { useNavigate } from 'react-router-dom'
import { useNavigate } from "react-router-dom"
import { getToken } from '../utils/auth'
function WithToken({ children }: PropsWithChildren) { function WithToken({ children }: PropsWithChildren) {
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => { useEffect(() => {
if (!getToken()) { if (!getToken()) {
return navigate("/login") return navigate('/login')
} }
}, [navigate]) }, [navigate])

View File

@ -1,21 +1,9 @@
import AnnouncementDetails from "./AnnouncementDetails" export { default as AnnouncementDetails } from './AnnouncementDetails'
import BottomNavBar from "./BottomNavBar" export { default as BottomNavBar } from './BottomNavBar'
import Filters from "./Filters" export { default as Filters } from './Filters'
import LineDot from "./LineDot" export { default as LineDot } from './LineDot'
import LocationMarker from './LocationMarker' export { default as LocationMarker } from './LocationMarker'
import TrashboxMarkers from './TrashboxMarkers' export { default as TrashboxMarkers } from './TrashboxMarkers'
import WithToken from './WithToken' export { default as WithToken } from './WithToken'
import ClickHandler from './ClickHandler' export { default as ClickHandler } from './ClickHandler'
import AuthForm from "./AuthForm" export { default as AuthForm } from './AuthForm'
export {
AnnouncementDetails,
BottomNavBar,
Filters,
LineDot,
LocationMarker,
TrashboxMarkers,
WithToken,
ClickHandler,
AuthForm,
}

View File

@ -1,3 +1,3 @@
const API_URL = "/api" const API_URL = '/api'
export { API_URL } export { API_URL }

View File

@ -1,15 +1,15 @@
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from 'react'
import { API_URL } from "../../config" import { API_URL } from '../../config'
import { isLiteralUnion } from "../../utils/types" import { isLiteralUnion } from '../../utils/types'
import { handleHTTPErrors } from "../../utils" import { handleHTTPErrors } from '../../utils'
const addErrors = ["Не удалось опубликовать объявление", 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const const addErrors = ['Не удалось опубликовать объявление', 'Неверный ответ от сервера', 'Неизвестная ошибка'] as const
type AddError = typeof addErrors[number] type AddError = typeof addErrors[number]
const isAddError = (obj: unknown): obj is AddError => isLiteralUnion(obj, addErrors) const isAddError = (obj: unknown): obj is AddError => isLiteralUnion(obj, addErrors)
const buttonStates = ["Опубликовать", "Загрузка...", "Опубликовано", "Отменено"] as const const buttonStates = ['Опубликовать', 'Загрузка...', 'Опубликовано', 'Отменено'] as const
type ButtonState = typeof buttonStates[number] | AddError type ButtonState = typeof buttonStates[number] | AddError
type AddResponse = { type AddResponse = {
@ -21,26 +21,26 @@ const isAddResponse = (obj: unknown): obj is AddResponse =>
const useAddAnnouncement = () => { const useAddAnnouncement = () => {
const [status, setStatus] = useState<ButtonState>("Опубликовать") const [status, setStatus] = useState<ButtonState>('Опубликовать')
const timerIdRef = useRef<number>() const timerIdRef = useRef<number>()
const abortControllerRef = useRef<AbortController>() const abortControllerRef = useRef<AbortController>()
const doAdd = async (formData: FormData) => { const doAdd = async (formData: FormData) => {
if (status === "Загрузка...") { if (status === 'Загрузка...') {
abortControllerRef.current?.abort() abortControllerRef.current?.abort()
setStatus("Отменено") setStatus('Отменено')
timerIdRef.current = setTimeout(() => setStatus("Опубликовать"), 3000) timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 3000)
return return
} }
setStatus("Загрузка...") setStatus('Загрузка...')
const abortController = new AbortController() const abortController = new AbortController()
abortControllerRef.current = abortController abortControllerRef.current = abortController
try { try {
const res = await fetch(API_URL + "/announcement", { const res = await fetch(API_URL + '/announcement', {
method: 'PUT', method: 'PUT',
body: formData, body: formData,
signal: abortController.signal signal: abortController.signal
@ -53,13 +53,13 @@ const useAddAnnouncement = () => {
if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера') if (!isAddResponse(data)) throw new Error('Неверный ответ от сервера')
if (!data.Answer) { if (!data.Answer) {
throw new Error("Не удалось опубликовать объявление") throw new Error('Не удалось опубликовать объявление')
} }
setStatus("Опубликовано") setStatus('Опубликовано')
} catch (err) { } catch (err) {
setStatus(isAddError(err) ? err : "Неизвестная ошибка") setStatus(isAddError(err) ? err : 'Неизвестная ошибка')
timerIdRef.current = setTimeout(() => setStatus("Опубликовать"), 10000) timerIdRef.current = setTimeout(() => setStatus('Опубликовать'), 10000)
} }
} }

View File

@ -1,7 +1,8 @@
import { useState } from "react" import { useState } from 'react'
import { API_URL } from "../../config"
import { isConst, isObject } from "../../utils/types" import { API_URL } from '../../config'
import { handleHTTPErrors } from "../../utils" import { isConst, isObject } from '../../utils/types'
import { handleHTTPErrors } from '../../utils'
interface AuthData { interface AuthData {
email: string, email: string,
@ -24,11 +25,11 @@ type SignUpResponse = {
const isSignUpResponse = (obj: unknown): obj is SignUpResponse => ( const isSignUpResponse = (obj: unknown): obj is SignUpResponse => (
isObject(obj, { isObject(obj, {
"Success": isConst(true) 'Success': isConst(true)
}) || }) ||
isObject(obj, { isObject(obj, {
"Success": isConst(false), 'Success': isConst(false),
"Message": "string" 'Message': 'string'
}) })
) )
@ -38,8 +39,8 @@ interface LogInResponse {
} }
const isLogInResponse = (obj: unknown): obj is LogInResponse => isObject(obj, { const isLogInResponse = (obj: unknown): obj is LogInResponse => isObject(obj, {
"access_token": "string", 'access_token': 'string',
"token_type": isConst("bearer") 'token_type': isConst('bearer')
}) })
function useAuth() { function useAuth() {
@ -51,8 +52,8 @@ function useAuth() {
if (newAccount) { if (newAccount) {
try { try {
const res = await fetch(API_URL + "/signup", { const res = await fetch(API_URL + '/signup', {
method: "POST", method: 'POST',
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -64,7 +65,7 @@ function useAuth() {
const signupData: unknown = await res.json() const signupData: unknown = await res.json()
if (!isSignUpResponse(signupData)) { if (!isSignUpResponse(signupData)) {
throw new Error("Malformed server response") throw new Error('Malformed server response')
} }
if (signupData.Success === false) { if (signupData.Success === false) {
@ -83,7 +84,7 @@ function useAuth() {
username: data.email, username: data.email,
password: data.password password: data.password
}).toString(), { }).toString(), {
method: "POST", method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
} }
@ -92,7 +93,7 @@ function useAuth() {
const logInData: unknown = await res.json() const logInData: unknown = await res.json()
if (!isLogInResponse(logInData)) { if (!isLogInResponse(logInData)) {
throw new Error("Malformed server response") throw new Error('Malformed server response')
} }
const token = logInData.access_token const token = logInData.access_token
@ -111,4 +112,4 @@ function useAuth() {
return { doAuth, loading, error } return { doAuth, loading, error }
} }
export default useAuth export default useAuth

View File

@ -1,20 +1,20 @@
import { useState } from "react" import { useState } from 'react'
import { useNavigate } from "react-router-dom" import { useNavigate } from 'react-router-dom'
import { getToken } from "../../utils/auth" import { getToken } from '../../utils/auth'
import { API_URL } from "../../config" import { API_URL } from '../../config'
import { isObject } from "../../utils/types" import { isObject } from '../../utils/types'
import { handleHTTPErrors } from "../../utils" import { handleHTTPErrors } from '../../utils'
type BookResponse = { type BookResponse = {
Success: boolean Success: boolean
} }
const isBookResponse = (obj: unknown): obj is BookResponse => isObject(obj, { const isBookResponse = (obj: unknown): obj is BookResponse => isObject(obj, {
"Success": "boolean" 'Success': 'boolean'
}) })
type BookStatus = "" | "Загрузка..." | "Забронировано" | "Ошибка бронирования" type BookStatus = '' | 'Загрузка...' | 'Забронировано' | 'Ошибка бронирования'
function useBook(id: number) { function useBook(id: number) {
const navigate = useNavigate() const navigate = useNavigate()
@ -25,7 +25,7 @@ function useBook(id: number) {
const token = getToken() const token = getToken()
if (token) { if (token) {
setStatus("Загрузка...") setStatus('Загрузка...')
try { try {
@ -45,24 +45,24 @@ function useBook(id: number) {
const data: unknown = await res.json() const data: unknown = await res.json()
if (!isBookResponse(data)) { if (!isBookResponse(data)) {
throw new Error("Malformed server response") throw new Error('Malformed server response')
} }
if (data.Success === true) { if (data.Success === true) {
setStatus('Забронировано') setStatus('Забронировано')
} else { } else {
throw new Error("Server refused to book") throw new Error('Server refused to book')
} }
} }
catch (err) { catch (err) {
setStatus("Ошибка бронирования") setStatus('Ошибка бронирования')
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
console.log(err) console.log(err)
} }
} }
} else { } else {
return navigate("/login") return navigate('/login')
} }
} }

View File

@ -1,11 +1,11 @@
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from 'react'
import { handleHTTPErrors, isAborted } from '../../utils' import { handleHTTPErrors, isAborted } from '../../utils'
const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => { const useFetch = <T>(url: string, params: RequestInit | undefined, initialData: T, dataGuard: (obj: unknown) => obj is T) => {
const [data, setData] = useState(initialData) const [data, setData] = useState(initialData)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState("") const [error, setError] = useState('')
const abortControllerRef = useRef<AbortController>() const abortControllerRef = useRef<AbortController>()
@ -25,7 +25,7 @@ const useFetch = <T>(url: string, params: RequestInit | undefined, initialData:
}) })
.then(data => { .then(data => {
if (!dataGuard(data)) { if (!dataGuard(data)) {
throw new Error("Неверный ответ от сервера") throw new Error('Неверный ответ от сервера')
} }
setData(data) setData(data)
@ -33,7 +33,7 @@ const useFetch = <T>(url: string, params: RequestInit | undefined, initialData:
}) })
.catch(err => { .catch(err => {
if (err instanceof Error && !isAborted(err)) { if (err instanceof Error && !isAborted(err)) {
setError("Ошибка сети") setError('Ошибка сети')
} }
setLoading(false) setLoading(false)

View File

@ -1,7 +1,7 @@
import useFetch from './useFetch' import useFetch from './useFetch'
import { API_URL } from '../../config'
import { FiltersType, filterNames } from '../../utils/filters' import { FiltersType, filterNames } from '../../utils/filters'
import { isArrayOf, isObject } from '../../utils/types' import { isArrayOf, isObject } from '../../utils/types'
import { API_URL } from '../../config'
import { Category, isCategory } from '../../assets/category' import { Category, isCategory } from '../../assets/category'
const initialAnnouncements = { list_of_announcements: [], Success: true } const initialAnnouncements = { list_of_announcements: [], Success: true }
@ -12,8 +12,8 @@ type AnnouncementsListResponse = {
} }
const isAnnouncementsListResponse = (obj: unknown): obj is AnnouncementsListResponse => isObject(obj, { const isAnnouncementsListResponse = (obj: unknown): obj is AnnouncementsListResponse => isObject(obj, {
"list_of_announcements": obj => isArrayOf<AnnouncementResponse>(obj, isAnnouncementResponse), 'list_of_announcements': obj => isArrayOf<AnnouncementResponse>(obj, isAnnouncementResponse),
"Success": "boolean" 'Success': 'boolean'
}) })
type AnnouncementResponse = { type AnnouncementResponse = {
@ -33,19 +33,19 @@ type AnnouncementResponse = {
} }
const isAnnouncementResponse = (obj: unknown): obj is AnnouncementResponse => isObject(obj, { const isAnnouncementResponse = (obj: unknown): obj is AnnouncementResponse => isObject(obj, {
"id": "number", 'id': 'number',
"user_id": "number", 'user_id': 'number',
"name": "string", 'name': 'string',
"category": isCategory, 'category': isCategory,
"best_by": "number", 'best_by': 'number',
"address": "string", 'address': 'string',
"longtitude": "number", 'longtitude': 'number',
"latitude": "number", 'latitude': 'number',
"description": "string", 'description': 'string',
"src": "string?", 'src': 'string?',
"metro": "string", 'metro': 'string',
"trashId": "number?", 'trashId': 'number?',
"booked_by": "number" 'booked_by': 'number'
}) })
type Announcement = { type Announcement = {

View File

@ -1,9 +1,9 @@
import { LatLng } from "leaflet" import { LatLng } from 'leaflet'
import { API_URL } from "../../config" import { API_URL } from '../../config'
import { isArrayOf, isObject } from "../../utils/types" import { isArrayOf, isObject } from '../../utils/types'
import useFetch from "./useFetch" import useFetch from './useFetch'
import { isString } from "../../utils/types" import { isString } from '../../utils/types'
type Trashbox = { type Trashbox = {
Lat: number, Lat: number,
@ -13,15 +13,15 @@ type Trashbox = {
} }
const isTrashbox = (obj: unknown): obj is Trashbox => isObject(obj, { const isTrashbox = (obj: unknown): obj is Trashbox => isObject(obj, {
"Lat": "number", 'Lat': 'number',
"Lng": "number", 'Lng': 'number',
"Address": "string", 'Address': 'string',
"Categories": obj => isArrayOf<string>(obj, isString) 'Categories': obj => isArrayOf<string>(obj, isString)
}) })
const useTrashboxes = (position: LatLng) => { const useTrashboxes = (position: LatLng) => {
return useFetch( return useFetch(
API_URL + "/trashbox?" + new URLSearchParams({ API_URL + '/trashbox?' + new URLSearchParams({
lat: position.lat.toString(), lat: position.lat.toString(),
lng: position.lng.toString() lng: position.lng.toString()
}).toString(), }).toString(),
@ -32,4 +32,4 @@ const useTrashboxes = (position: LatLng) => {
} }
export type { Trashbox } export type { Trashbox }
export default useTrashboxes export default useTrashboxes

View File

@ -1,5 +1 @@
import useStoryDimensions from "./useStoryDimensions" export { default as useStoryDimensions } from './useStoryDimensions'
export {
useStoryDimensions,
}

View File

@ -17,8 +17,8 @@ function useStoryDimensions(maxRatio = 16/9) {
setWindowDimensions(getWindowDimensions()); setWindowDimensions(getWindowDimensions());
} }
window.addEventListener("resize", handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener("resize", handleResize); return () => window.removeEventListener('resize', handleResize);
}, []); }, []);
const height = windowDimensions.height - 56 const height = windowDimensions.height - 56
@ -31,4 +31,4 @@ function useStoryDimensions(maxRatio = 16/9) {
} }
} }
export default useStoryDimensions export default useStoryDimensions

View File

@ -2,4 +2,4 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
} }

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App.tsx' import App from './App.tsx'
import './index.css' import './index.css'

View File

@ -1,15 +1,14 @@
import { FormEventHandler, useEffect, useState } from "react" import { FormEventHandler, useEffect, useState } from 'react'
import { Form, Button, Card } from "react-bootstrap" import { Form, Button, Card } from 'react-bootstrap'
import { MapContainer, TileLayer } from 'react-leaflet' import { MapContainer, TileLayer } from 'react-leaflet'
import { latLng } from "leaflet" import { latLng } from 'leaflet'
import { ClickHandler, LocationMarker, TrashboxMarkers } from "../components" import { ClickHandler, LocationMarker, TrashboxMarkers } from '../components'
import { useAddAnnouncement, useTrashboxes } from "../hooks/api" import { useAddAnnouncement, useTrashboxes } from '../hooks/api'
import { isObject } from '../utils/types'
import { categories, categoryNames } from "../assets/category" import { handleHTTPErrors } from '../utils'
import { stations, lines, lineNames } from "../assets/metro" import { categories, categoryNames } from '../assets/category'
import { isObject } from "../utils/types" import { stations, lines, lineNames } from '../assets/metro'
import { handleHTTPErrors } from "../utils"
function AddPage() { function AddPage() {
const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227)) const [addressPosition, setAddressPosition] = useState(latLng(59.972, 30.3227))
@ -21,13 +20,13 @@ function AddPage() {
useEffect(() => { useEffect(() => {
void (async () => { void (async () => {
try { try {
const res = await fetch(location.protocol + "//nominatim.openstreetmap.org/search?format=json&q=" + address) const res = await fetch(location.protocol + '//nominatim.openstreetmap.org/search?format=json&q=' + address)
handleHTTPErrors(res) handleHTTPErrors(res)
const fetchData: unknown = await res.json() const fetchData: unknown = await res.json()
console.log("f", fetchData) console.log('f', fetchData)
} catch (err) { } catch (err) {
console.error(err) console.error(err)
@ -44,8 +43,8 @@ function AddPage() {
const fetchData: unknown = await res.json() const fetchData: unknown = await res.json()
if (!isObject<{ display_name: string }>(fetchData, { "display_name": "string" })) { if (!isObject<{ display_name: string }>(fetchData, { 'display_name': 'string' })) {
throw new Error("Malformed server response") throw new Error('Malformed server response')
} }
setAddress(fetchData.display_name) setAddress(fetchData.display_name)
@ -64,27 +63,27 @@ function AddPage() {
const formData = new FormData(event.currentTarget) const formData = new FormData(event.currentTarget)
formData.append("latitude", addressPosition.lat.toString()) formData.append('latitude', addressPosition.lat.toString())
formData.append("longtitude", addressPosition.lng.toString()) formData.append('longtitude', addressPosition.lng.toString())
formData.append("address", address) formData.append('address', address)
formData.set("bestBy", new Date((formData.get("bestBy") as number | null) || 0).getTime().toString()) formData.set('bestBy', new Date((formData.get('bestBy') as number | null) || 0).getTime().toString())
void doAdd(formData) void doAdd(formData)
} }
return ( return (
<Card className="m-4" style={{ height: 'calc(100vh - 3rem)' }}> <Card className='m-4' style={{ height: 'calc(100vh - 3rem)' }}>
<Card.Body style={{ overflowY: "auto" }} > <Card.Body style={{ overflowY: 'auto' }} >
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<Form.Group className="mb-3" controlId="name"> <Form.Group className='mb-3' controlId='name'>
<Form.Label>Заголовок объявления</Form.Label> <Form.Label>Заголовок объявления</Form.Label>
<Form.Control type="text" required name="name" /> <Form.Control type='text' required name='name' />
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="category"> <Form.Group className='mb-3' controlId='category'>
<Form.Label>Категория</Form.Label> <Form.Label>Категория</Form.Label>
<Form.Select required name="category"> <Form.Select required name='category'>
<option value="" hidden> <option value='' hidden>
Выберите категорию Выберите категорию
</option> </option>
{categories.map(category => {categories.map(category =>
@ -93,23 +92,23 @@ function AddPage() {
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="bestBy"> <Form.Group className='mb-3' controlId='bestBy'>
<Form.Label>Срок годности</Form.Label> <Form.Label>Срок годности</Form.Label>
<Form.Control type="date" required name="bestBy" /> <Form.Control type='date' required name='bestBy' />
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="address"> <Form.Group className='mb-3' controlId='address'>
<Form.Label>Адрес выдачи</Form.Label> <Form.Label>Адрес выдачи</Form.Label>
<div className="mb-3"> <div className='mb-3'>
<MapContainer <MapContainer
scrollWheelZoom={false} scrollWheelZoom={false}
style={{ width: "100%", height: 400 }} style={{ width: '100%', height: 400 }}
center={addressPosition} center={addressPosition}
zoom={13} zoom={13}
> >
<TileLayer <TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
<LocationMarker <LocationMarker
address={address} address={address}
@ -124,26 +123,26 @@ function AddPage() {
<p>Адрес: {address}</p> <p>Адрес: {address}</p>
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="description"> <Form.Group className='mb-3' controlId='description'>
<Form.Label>Описание</Form.Label> <Form.Label>Описание</Form.Label>
<Form.Control as="textarea" name="description" rows={3} placeholder="Укажите свои контакты, а так же, когда вам будет удобно передать продукт" /> <Form.Control as='textarea' name='description' rows={3} placeholder='Укажите свои контакты, а так же, когда вам будет удобно передать продукт' />
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="src"> <Form.Group className='mb-3' controlId='src'>
<Form.Label>Иллюстрация (фото или видео)</Form.Label> <Form.Label>Иллюстрация (фото или видео)</Form.Label>
<Form.Control <Form.Control
type="file" type='file'
name="src" name='src'
accept="video/mp4,video/mkv, video/x-m4v,video/*, image/*" accept='video/mp4,video/mkv, video/x-m4v,video/*, image/*'
capture="environment" capture='environment'
/> />
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="metro"> <Form.Group className='mb-3' controlId='metro'>
<Form.Label> <Form.Label>
Станция метро Станция метро
</Form.Label> </Form.Label>
<Form.Select name="metro"> <Form.Select name='metro'>
<option value=""> <option value=''>
Укажите ближайщую станцию метро Укажите ближайщую станцию метро
</option> </option>
{lines.map( {lines.map(
@ -157,9 +156,9 @@ function AddPage() {
</Form.Select> </Form.Select>
</Form.Group> </Form.Group>
<Form.Group className="mb-3" controlId="password"> <Form.Group className='mb-3' controlId='password'>
<Form.Label>Пункт сбора мусора</Form.Label> <Form.Label>Пункт сбора мусора</Form.Label>
<div className="mb-3"> <div className='mb-3'>
{trashboxes.loading {trashboxes.loading
? ( ? (
<div style={{ height: 400 }}> <div style={{ height: 400 }}>
@ -169,19 +168,19 @@ function AddPage() {
trashboxes.error ? ( trashboxes.error ? (
<p <p
style={{ height: 400 }} style={{ height: 400 }}
className="text-danger" className='text-danger'
>{trashboxes.error}</p> >{trashboxes.error}</p>
) : ( ) : (
<MapContainer <MapContainer
scrollWheelZoom={false} scrollWheelZoom={false}
style={{ width: "100%", height: 400 }} style={{ width: '100%', height: 400 }}
center={addressPosition} center={addressPosition}
zoom={13} zoom={13}
className="" className=''
> >
<TileLayer <TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/> />
<TrashboxMarkers <TrashboxMarkers
trashboxes={trashboxes.data} trashboxes={trashboxes.data}
@ -201,7 +200,7 @@ function AddPage() {
)} )}
</Form.Group> </Form.Group>
<Button variant="success" type="submit"> <Button variant='success' type='submit'>
{status} {status}
</Button> </Button>
</Form> </Form>

View File

@ -1,22 +1,21 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import Stories from 'react-insta-stories' import Stories from 'react-insta-stories'
import { Story } from 'react-insta-stories/dist/interfaces'
import { BottomNavBar, AnnouncementDetails, Filters } from '../components' import { BottomNavBar, AnnouncementDetails, Filters } from '../components'
import { useStoryDimensions } from '../hooks' import { useStoryDimensions } from '../hooks'
import { useHomeAnnouncementList } from '../hooks/api' import { useHomeAnnouncementList } from '../hooks/api'
import { defaultFilters } from '../utils/filters' import { defaultFilters } from '../utils/filters'
import { Announcement } from '../hooks/api/useHomeAnnouncementList'
import puffSpinner from '../assets/puff.svg' import puffSpinner from '../assets/puff.svg'
import { categoryGraphics } from '../assets/category' import { categoryGraphics } from '../assets/category'
import { Announcement } from '../hooks/api/useHomeAnnouncementList'
import { Story } from 'react-insta-stories/dist/interfaces'
function generateStories(announcements: Announcement[]): Story[] { function generateStories(announcements: Announcement[]): Story[] {
return announcements.map(announcement => { return announcements.map(announcement => {
return ({ return ({
id: announcement.id, id: announcement.id,
url: announcement.src || categoryGraphics[announcement.category], url: announcement.src || categoryGraphics[announcement.category],
type: announcement.src?.endsWith("mp4") ? "video" : undefined, type: announcement.src?.endsWith('mp4') ? 'video' : undefined,
seeMore: ({ close }: { close: () => void }) => <AnnouncementDetails close={close} announcement={announcement} /> seeMore: ({ close }: { close: () => void }) => <AnnouncementDetails close={close} announcement={announcement} />
}) })
}) })
@ -32,7 +31,7 @@ function fallbackGenerateStories(announcementsFetch: ReturnType<typeof useHomeAn
return fallbackStory(announcementsFetch.error, true) return fallbackStory(announcementsFetch.error, true)
if (stories.length === 0) if (stories.length === 0)
return fallbackStory("Здесь пока пусто") return fallbackStory('Здесь пока пусто')
return stories return stories
} }
@ -43,7 +42,7 @@ const fallbackStory = (text = '', isError = false): Story[] => [{
useEffect(() => { action('pause') }, [action]) useEffect(() => { action('pause') }, [action])
return ( return (
<div style={{ margin: 'auto' }} className={isError ? "text-danger" : ''}> <div style={{ margin: 'auto' }} className={isError ? 'text-danger' : ''}>
{text || <img src={puffSpinner} />} {text || <img src={puffSpinner} />}
</div> </div>
) )
@ -62,7 +61,7 @@ function HomePage() {
return (<> return (<>
<Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} /> <Filters filter={filter} setFilter={setFilter} filterShown={filterShown} setFilterShown={setFilterShown} />
<div style={{ display: "flex", justifyContent: "center", backgroundColor: "rgb(17, 17, 17)" }}> <div style={{ display: 'flex', justifyContent: 'center', backgroundColor: 'rgb(17, 17, 17)' }}>
<Stories <Stories
stories={stories} stories={stories}
defaultInterval={11000} defaultInterval={11000}

View File

@ -1,9 +1,9 @@
import { FormEventHandler } from 'react' import { FormEventHandler } from 'react'
import { Card, Tabs, Tab } from "react-bootstrap" import { Card, Tabs, Tab } from 'react-bootstrap'
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import { useAuth } from "../hooks/api"; import { useAuth } from '../hooks/api';
import { setToken } from "../utils/auth"; import { setToken } from '../utils/auth';
import { AuthForm } from '../components'; import { AuthForm } from '../components';
function LoginPage() { function LoginPage() {
@ -24,22 +24,22 @@ function LoginPage() {
password: formData.get('password') as string password: formData.get('password') as string
} }
const token = import.meta.env.PROD ? await doAuth(data, newAccount) : "a" const token = import.meta.env.PROD ? await doAuth(data, newAccount) : 'a'
if (token) { if (token) {
setToken(token) setToken(token)
navigate("/") navigate('/')
} }
} }
return ( return (
<Card className="m-4"> <Card className='m-4'>
<Card.Body> <Card.Body>
<Tabs defaultActiveKey="register" fill justify className="mb-3"> <Tabs defaultActiveKey='register' fill justify className='mb-3'>
<Tab eventKey="register" title="Регистрация"> <Tab eventKey='register' title='Регистрация'>
<AuthForm handleAuth={handleAuth(true)} register={true} loading={loading} error={error} /> <AuthForm handleAuth={handleAuth(true)} register={true} loading={loading} error={error} />
</Tab> </Tab>
<Tab eventKey="login" title="Вход"> <Tab eventKey='login' title='Вход'>
<AuthForm handleAuth={handleAuth(false)} register={false} loading={loading} error={error} /> <AuthForm handleAuth={handleAuth(false)} register={false} loading={loading} error={error} />
</Tab> </Tab>
</Tabs> </Tabs>
@ -48,4 +48,4 @@ function LoginPage() {
) )
} }
export default LoginPage export default LoginPage

View File

@ -1,9 +1,9 @@
import { Link } from "react-router-dom" import { Link } from 'react-router-dom'
function UserPage() { function UserPage() {
/* TODO */ /* TODO */
return <h1>For Yet Go <Link to="/">Home</Link></h1> return <h1>For Yet Go <Link to='/'>Home</Link></h1>
} }
export default UserPage export default UserPage

View File

@ -1,5 +1,5 @@
const getToken = () => { const getToken = () => {
const token = localStorage.getItem("Token") const token = localStorage.getItem('Token')
/* check expirity */ /* check expirity */
@ -7,7 +7,7 @@ const getToken = () => {
} }
const setToken = (token: string) => { const setToken = (token: string) => {
localStorage.setItem("Token", token) localStorage.setItem('Token', token)
} }
export { getToken, setToken } export { getToken, setToken }

View File

@ -1,6 +1,6 @@
import { Announcement } from "../hooks/api/useHomeAnnouncementList" import { Announcement } from '../hooks/api/useHomeAnnouncementList'
const filterNames = ["userId", "category", "metro", "bookedBy"] as const const filterNames = ['userId', 'category', 'metro', 'bookedBy'] as const
type FilterNames = typeof filterNames[number] type FilterNames = typeof filterNames[number]
type FiltersType = Partial<Pick<Announcement, FilterNames>> type FiltersType = Partial<Pick<Announcement, FilterNames>>

View File

@ -4,11 +4,11 @@ const handleHTTPErrors = (res: Response) => {
if (!res.ok) { if (!res.ok) {
switch (res.status) { switch (res.status) {
case 401: case 401:
throw new Error("Ошибка авторизации") throw new Error('Ошибка авторизации')
case 404: case 404:
throw new Error("Объект не найден") throw new Error('Объект не найден')
default: { default: {
throw new Error("Ошибка ответа от сервера") throw new Error('Ошибка ответа от сервера')
} }
} }
} }

View File

@ -5,7 +5,7 @@ const isRecord = <K extends string | number | symbol>(obj: unknown): obj is Reco
obj !== null obj !== null
) )
type Primitive = "bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined" type Primitive = 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'
type PropertyGuard = Primitive | `${Primitive}?` | ((obj: unknown) => boolean) type PropertyGuard = Primitive | `${Primitive}?` | ((obj: unknown) => boolean)